CUDA番外 -- thrust简介

注意:使用thrust必须将文件后缀名设定为cu,在windows上,所有cuda代码文件必须使用带BOM头的utf-8编码、或者GBK编码!

§vector

thrust有两种vector:host_vector和device_vector,一种运行于主机之上,一种运行于GPU上。两者提供的接口相同,所有能使用host_vector的地方,也能使用device_vector,算法本身不区分host_vector和device_vector,但两者的iterator不一样,隐藏了两者的细节。注意:device_vector也能进行随机访问,但会触发cudaMemcpy操作,引起性能损失,因此应该尽量避免dev_vec[0]这样的操作。

§算法

thrust提供了一系列与stl类似的算法,用于简化操作,主要有以下几类:searching、copying、reduction、recording、transformation。以下为几个常用的函数模板使用示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include <iostream>
#include <cmath>

#include <thrust/device_vector.h>
#include <thrust/host_vector.h>

thrust::device_vector<int> dev_v1(10);
thrust::device_vector<int> dev_v2(10);
thrust::device_vector<int> dev_v3(10);

// 以0到9进行填充
#include <thrust/sequence.h>
thrust::sequence(dev_v1.begin(), dev_v1.end());

// v2=-v1
#include <thrust/transform.h>
#include <thrust/functional.h>
thrust::transform(
    dev_v1.begin(), dev_v1.end(),
    dev_v2.begin(), thrust::negate<int>()
);

// 以2进行填充
#include <thrust/fill.h>
thrust::fill(dev_v2.begin(), dev_v2.end(), 2);

// v3 = v1 % v2
thrust::transform(
    dev_v1.begin(), dev_v1.end(),
    dev_v2.begin(), dev_v3.begin(),
    thrust::modulus<int>()
);

// 查找与替换
#include <thrust/replace.h>
thrust::replace(dev_v3.begin(), dev_v3.end(), 1, 5);

// 输出
#include <thrust/copy.h>
thrust::copy(
    dev_v3.begin(), dev_v3.end(),
    std::ostream_iterator<int>(std::cout, "\n")
);

// 归约,3个一样
#include <thrust/reduce.h>
int sum = thrust::reduce(dev_v3.begin(), dev_v3.end(), int(0), thrust::plus<int>());
int sum = thrust::reduce(dev_v3.begin(), dev_v3.end(), int(0));
int sum = thrust::reduce(dev_v3.begin(), dev_v3.end());

// 数数
#include <thrust/count.h>
int count = thrust::count(dev_v3.begin(), dev_v3.end(), 1);  // 1的个数

// 求模(l2范数),也可以使用lambda表达式,但要注意cuda版本间的区别
#include <thrust/transform_reduce.h>
template <class T>
class square {
public:
    __host__ __device__ T operator() (const T &x) const {
        return x * x;
    }
};
float norm = std::sqrt(thrust::transform_reduce(
    dev_v3.begin(), dev_v3.end(), sqaure<int>(), 0, thrust::plus<int>())
);

// 累积
#include <thrust/scan.h>
thrust::inclusive_scan(dev_v1.begin(), dev_v1.end(), dev_v1.begin());
thrust::exclusive_scan(dev_v1.begin(), dev_v1.end(), dev_v1.begin());

// 排序
#include <thrust/sort.h>
thrust::sort(dev_v1.begin(), dev_v1.end());

const int N = 6;
int  keys[] = { 1,  4,  2,  8,  5,  7};
char values[] = {'a', 'b', 'c', 'd', 'e', 'f'};
thrust::sort_by_key(keys, keys + N, values);
// now keys is 1, 2, 4, 5, 7, 8
// now values is a, c, b, e, f, d

thrust::stable_sort(dev_v1.begin(), dev_v1.end(), thrust::greater<int>());

注意:thrust库的归约算法效率比较低,影响的接口有reduce和transform_reduce等,推荐自己实现。

§指针

thrust在和其它的函数配合时,需要将容器转换为指针或者把指针转换为容器,使用device_ptr和raw_pointer_cast即可:

1
2
3
4
5
6
7
8
9
size_t N = 10;
int *raw_ptr = 0;
cudaMalloc((void **)&raw_ptr, N * sizeof(int));
thrust::device_ptr<int> dev_ptr(raw_ptr);  // 这是特性,不作这样的转换可能出错
thrust::fill(dev_ptr, dev_ptr + N, int(0));

size_t N = 10;
thrust::device_ptr<int> dev_ptr = thrust::device_malloc<int>(N);
int *raw_ptr = thrust::raw_pointer_cast(dev_ptr);

thrust在与cublas之类的库合作时,需要把容器的数据首地址传给cublas函数,需要对其进行转换:

1
2
3
4
size_t N = 10000;
thrust::device_vector<float> dev_A(N * N);
float *dev_A_raw = thrust::raw_pointer_cast(dev_A.data());
cublas(..., dev_A_raw, ...); 

下面是一个直接使用raw_pointer引起错误的例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#include <thrust/device_ptr.h>
#include <thrust/host_vector.h>
#include <thrust/transform.h>
#include <thrust/functional.h>
#include <thrust/iterator/counting_iterator.h>

struct GenerateKernel : public thrust::unary_function<int, int> {
    __device__ int operator()(int i) const {
        return i;
    }
};

int N = 10000;
const thrust::counting_iterator<int> first(0);
const thrust::counting_iterator<int> last(N);

int *raw_ptr = nullptr;
cudaMalloc(&raw_ptr, sizeof(int) * N);
thrust::transform(first, last, raw_ptr, GenerateKernel{}); // error
thrust::host_vector<int> vec(raw_ptr, raw_ptr + N);        // error

最后两句有错,正确的语句如下:

1
2
3
4
5
thrust::transform(first, last, thrust::device_ptr<int>(raw_ptr), GenerateKernel{});
thrust::host_vector<int> vec(
    thrust::device_ptr<int>(raw_ptr),
    thrust::device_ptr<int>(raw_ptr) + N
);

原因是thrust可以同时用于GPU和CPU平台,因此迭代器或者device_ptr中必须带有平台信息,否则无法判定应该使用CPU还是GPU来计算,所以不能使用裸指针。

加载评论