【发布时间】:2013-04-11 01:45:44
【问题描述】:
假设我们有这个排序数组
0 1 1 1 1 2 2 2 2 2 3 10 10 10
我想有效地找到元素变化的位置。例如,在我们的数组中,位置如下:
0 1 5 10 11
我知道有几个库 (Thrust) 可以实现这一点,但是我想创建自己的高效实现以用于教育目的。
你可以在这里找到完整的代码:http://pastebin.com/Wu34F4M2
它也包括验证。
内核是如下函数:
__global__ void findPositions(int *device_data,
int totalAmountOfValuesPerThread, int* pos_ptr, int N){
int res1 = 9999999;
int res2 = 9999999;
int index = totalAmountOfValuesPerThread*(threadIdx.x +
blockIdx.x*blockDim.x);
int start = index; //from this index each thread will begin searching
if(start < N){ //if the index is out of bounds do nothing
if(start!=0){ //if start is not in the beginning, check the previous value
if(device_data[start-1] != device_data[start]){
res1 = start;
}
}
else res1 = start; //since it's the
//beginning we update the first output buffer for the thread
pos_ptr[index] = res1;
start++; //move to the next place and see if the
//second output buffer needs updating or not
if(start < N && device_data[start] != device_data[start-1]){
res2 = start;
}
if((index+1) < N)
pos_ptr[index+ 1] = res2;
}
}
我创建了这么多线程,因此每个线程都必须处理数组的两个值。
-
device_data将所有数字存储在数组中 -
totalAmountOfValuesPerThread在这种情况下是每个线程必须使用的值的总量 -
pos_ptr与device_data的长度相同,每个线程将缓冲区的结果写入此device_vector -
N是device_data数组中的数字总数
在名为res1 和res2 的输出缓冲区中,每个线程要么保存以前未找到的位置,要么保持原样。
例子:
0 <---- thread 1
1
1 <---- thread 2
1
2 <---- thread 3
2
3 <---- thread 4
每个线程的输出缓冲区,假设大数 9999999 是inf 将是:
thread1 => {res1=0, res2=1}
thread2 => {res1=inf, res2=inf}
thread3 => {res1=4, res2=inf}
thread4 => {res1=6, res2=inf}
每个线程都会更新pos_ptrdevice_vector,因此这个向量将具有以下结果:
pos_ptr =>{0, 1, inf, inf, 4, inf, 6, inf}
完成内核后,我使用库Thrust 对向量进行排序,并将结果保存在名为host_pos 的宿主向量中。所以host_pos 向量将具有以下内容:
host_pos => {0, 1, 4, 6, inf, inf, inf, inf}
这个实现很糟糕,因为
- 内核内部创建了很多分支,因此会出现低效的wrap处理
- 我假设每个线程只使用 2 个值,这是非常低效的,因为创建了太多线程
- 我创建并传输了一个
device_vector,它与输入一样大,也驻留在全局内存中。每个线程访问这个向量以便写入结果,这是非常低效的。
这是每个块中有512 线程时输入大小为1 000 000 的测试用例。
CPU time: 0.000875688 seconds
GPU time: 1.35816 seconds
另一个大小为10 000 000输入的测试用例
CPU time: 0.0979209
GPU time: 1.41298 seconds
请注意,CPU 版本几乎慢了 100 倍,而 GPU 几乎相同。
不幸的是我的GPU没有足够的内存,所以让我们试试50 000 000
CPU time: 0.459832 seconds
GPU time: 1.59248 seconds
据我所知,对于大量输入,我的 GPU 实现可能会变得更快,但我相信更有效的方法可能会使实现更快,即使对于较小的输入也是如此。
为了让我的算法运行得更快,您会建议什么设计?不幸的是,我想不出更好的了。
提前谢谢你
【问题讨论】:
-
你应该把你使用的编译命令。因此,其他人可以更轻松地帮助您,并且每个人都可以拥有相同的 nvcc 选项(例如使用的架构)。
-
另外,请注意您正在为 GPU 实现计时内存分配和传输。请记住,这些操作可能特别慢,您可以通过运行
nvvp来更好地了解这一点。 -
快速查看 10000000 个元素的时间线表明,只有 3~4% 的计算时间花费在内核中,其余的花费在 Thrust 的扫描和缩减上(使用 GeForce GT 650M 测试)。
-
你好对不起我之前没看到消息(我去睡觉了),编译命令只是
nvcc test.cu -O3如果我完全不使用排序会更有效吗?我仍然必须编写每个缓冲区的结果,以便以后能够拥有它们。不幸的是,拥有一个向量不是一个好的选择,因为如果我是正确的,它是不受支持的。我想不出一种方法可以避免在线程之间使用如此多的共享输出内存......我想避免分支因素是不可能的,因为每个线程都必须知道它是否正在读取绑定值。 -
仅供参考,能否分享一下推力功能来做这个?
标签: c++ cuda parallel-processing gpu