Using functors in Thrust

Let's see how we can use a concept known as functors in Thrust. In C++, a functor is a class or struct object that looks and acts like a function; this lets us use something that looks and acts like a function, but can hold some parameters that don't have to be set every time it is used.

Let's start a new Thrust program with the appropriate include statements, and use the standard namespace:

#include <thrust/host_vector.h>
#include <thrust/device_vector.h>
#include <iostream>
using namespace std;

Now, let's set up a basic functor. We will use a struct to represent this, rather than class. This will be a weighted multiplication function, and we will store the weight in a float called w. We will make a constructor that sets up the weight with a default value of 1:

struct multiply_functor {
float w;
multiply_functor(float _w = 1) : w(_w) {}

We will now set up our functor with the operator() keyword; this will indicate to the compiler to treat the following block of code as the default function for objects of this type. Remember that this will be running on the GPU as a device function, so we precede the whole thing with __device__. We indicate the inputs with parentheses and output the appropriate value, which is just a scaled multiple. Now, we can close off the definition of our struct with };:

    __device__ float operator() (const float & x, const float & y) { 
return w * x * y;
}
};

Now, let's use this to make a basic dot product function; recall that this requires a pointwise multiplication between two arrays, followed by a reduce type sum. Let's start by declaring our function and creating a new vector, z, that will hold the values of the point-wise multiplication:

float dot_product(thrust::device_vector<float> &v, thrust::device_vector<float> &w ), thrust::device_vector<float> &z)
{
thrust::device_vector<float> z(v.size());

We will now use Thrust's transform operation, which will act on the inputs of v and w point-wise, and output into z. Notice how we input the functor into the last slot of transform; by using the plain closed parentheses like so, it will use the default value of the constructor (w = 1) so that this will act as a normal, non-weighted/scaled dot product:

thrust::transform(v.begin(), v.end(), w.begin(), z.begin(), multiply_functor());

We can now sum over z with Thrust's reduce function. Let's just return the value:

return thrust::reduce(z.begin(), z.end());
}

We're done. Now, let's write some test code—we'll just take the dot product of the vectors [1,2,3] and [1,1,1], which will be easy for us to check. (This will be 6.)

Let's just set up the first vector, v, using push_back:

int main(void)
{
thrust::device_vector<float> v;
v.push_back(1.0f);
v.push_back(2.0f);
v.push_back(3.0f);

We can now declare a vector, w, to be of size 3, and we can set its default values to 1 using Thrust's fill function, like so:

thrust::device_vector<float> w(3);
thrust::fill(w.begin(), w.end(), 1.0f);

Let's do a check to make sure that our values are set correctly by outputting their values to cout:

for (int i = 0; i < v.size(); i++)
cout << "v[" << i << "] == " << v[i] << endl;

for (int i = 0; i < w.size(); i++)
cout << "w[" << i << "] == " << w[i] << endl;

Now, we can check the output of our dot product, and then return from the program:

cout << "dot_product(v , w) == " << dot_product(v,w) << endl;
return 0;
}

Let's compile this (from the command line in both Linux or Windows by using nvcc thrust_dot_product.cu -o thrust_dot_product) and run it:

The code for this is also available in the thrust_dot_product.cu file in the Chapter08 directory in this book's repository.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.218.151.44