我有1000个元素的大数组,我想使用CUDA计算这个大数组中大小为100的大连续子序列的总和。 这里是一个小尺寸的说明性例子。数组的大小为20,序列的大小为5.
tab = [80,12,14,5,70,9,26,30,8,12,16,15,60,12,38,32,17,67,19,11]
序列如下:
S1= 80+12+14+5+70
S2= 12+14+5+70+9
S3= 14+5+70+9+26
....
您是否有一个有效的想法,使用CUDA并行化1000个元素的数组和100的序列来并行化这个任务?
答案 0 :(得分:3)
一些前言评论:
在这种情况下,我们可以考虑是否可以利用prefix sum来帮助我们。前缀和存在快速并行方法,因此这使得它具有吸引力。前缀和只是表示输入序列中所有先前数字之和的数字输出序列。所以,如果我有这样的数组:
1, 1, 3, 2, 0, 1
独占前缀总和将是:
0, 1, 2, 5, 7, 7, 8
exclusive 此处表示当前总和位置包含所有先前的值,但排除当前位置的数据项。对于独占前缀总和,请注意我们可以(如果我们希望)生成"有用"序列的数据比输入序列长度长1。
前缀总和可以很容易地适应您要求的问题。假设我的上述序列,我们想要计算3的子序列。我们可以取前缀和,并从中减去相同的前缀和序列,将右移动3(序列长度),就像这样:
0, 1, 2, 5, 7, 7, 8
- 0, 1, 2, 5, 7, 7, 8
= 5, 6, 5, 3
在这种情况下,这个序列(5,6,5,3)将是期望的答案。
以下是一个完整的例子,仅限于推力。我已经完成了它,因为在CUDA中编写快速并行前缀和并不是一件容易的事情,因此我更愿意使用(并建议其他人使用)库实现。如果你想探索如何编写你自己的并行前缀和,你可以探索推力,这是开源,或阅读this作为介绍。
这是一个功能齐全的例子:
$ cat t1279.cu
#include <thrust/device_vector.h>
#include <thrust/scan.h>
#include <thrust/transform.h>
#include <thrust/functional.h>
#include <thrust/copy.h>
#include <iostream>
const int arr_size = 1000;
const int seq_len = 100;
typedef int mytype;
int main(){
// test case
mytype t_arr[] = {80,12,14,5,70,9,26,30,8,12,16,15,60,12,38,32,17,67,19,11,0};
int t_arr_size = sizeof(t_arr)/sizeof(mytype);
int t_seq_len = 5;
thrust::device_vector<mytype> d_arr1(t_arr, t_arr+t_arr_size);
thrust::device_vector<mytype> d_res1(t_arr_size);
thrust::device_vector<mytype> d_out1(t_arr_size-t_seq_len);
thrust::exclusive_scan(d_arr1.begin(), d_arr1.end(), d_res1.begin());
thrust::transform(d_res1.begin()+t_seq_len, d_res1.end(), d_res1.begin(), d_out1.begin(), thrust::minus<mytype>());
thrust::copy_n(d_out1.begin(), t_arr_size-t_seq_len, std::ostream_iterator<mytype>(std::cout, ","));
std::cout << std::endl;
// case with larger array length and larger sequence length
thrust::device_vector<mytype> d_arr(arr_size+1, 1);
thrust::device_vector<mytype> d_res(arr_size+1);
thrust::device_vector<mytype> d_out(arr_size+1-seq_len);
thrust::inclusive_scan(d_arr.begin(), d_arr.end(), d_res.begin());
thrust::transform(d_res.begin()+seq_len, d_res.end(), d_res.begin(), d_out.begin(), thrust::minus<mytype>());
// validate
for (int i = 0; i < arr_size+1-seq_len; i++) {
mytype t = d_out[i];
if (t != seq_len) {std::cout << "mismatch at: " << i << "was: " << t << "should be: " << seq_len << std::endl; return 1;}
}
return 0;
}
$ nvcc -arch=sm_35 -o t1279 t1279.cu
$ ./t1279
181,110,124,140,143,85,92,81,111,115,141,157,159,166,173,146,
$
答案 1 :(得分:1)
之前回答了序列大小= 4,现在这仅仅是大小= 100。要么你的内核适应这个类似的问题,要么你在cpu上解决这个问题,因为cpu应该能够在一个单独的1000-10000个周期内解决这个问题(N = 1000,L = 100)(复杂度为O(N))核心虽然gpu的许多周期都是未使用的,但只有高级的gpu可以进行负载平衡以使用这些空闲资源。 CPU甚至可以在将数组发送到gpu之前解决这个问题。但是如果必须在GPU(两个内核之间)上发生这种情况,那么我可以建议Robert Crovella
的答案中仅与硬件相关的优化,例如:
我不知道cuda,但它应该能够使用多个内存限定符来使用更多的gpu芯片区域来更快地提供数据,就像opencl一样。
如果共享内存和全局内存有不同/单独的管道到达流处理器,那么你甚至可以使用全局内存来完成某些工作(我的意思是,不要将所有内容加载到共享,而是将一些内容从全局加载而不是让它使用所有shared_lines + global_lines)
32M元素阵列可能无法从第一次优化中获得帮助,但您仍然可以使用FP64累加器来拥有多个临时累加器(例如,每个线程使用8个FP32累加器和1个FP64累加器(使用FMA来减少其延迟,这可能是隐藏在这8个累加器后面,所以整个数组元素被同等对待)但是如果常量内存或纹理缓存有50000个元素的空间,它可以服务250个组(每个有200个元素来自这种类型的内存)或1250个组(每个都有50来自这个内存,150来自其他(共享,...))并且仍然可以稍微提高性能。(例如,100k元素阵列需要1k组,%25个线程使用常量内存)