假设我们有这个排序数组
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_ptr
device_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}
这种实施很糟糕,因为
device_vector
,并且也驻留在全局内存中。每个线程访问此向量以便写入结果,这是非常低效的。以下是在每个块中包含1 000 000
个线程时输入大小为512
的测试用例。
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实现可能会变得更快,但我相信更高效的方法可能会使实现更快,即使是较小的输入。
为了让我的算法运行得更快,您会建议使用什么设计?不幸的是,我想不出更好的事情。
提前谢谢
答案 0 :(得分:4)
我真的不明白为什么你认为这很糟糕的原因。线程太多了?太多线程的定义是什么?每个输入元素一个线程是CUDA程序中非常常见的线程策略。
因为您似乎愿意考虑在大部分工作中使用推力(例如,您在标记数据后愿意调用推力::排序)并考虑到BenC的观察(您正在消费大量的时间试图优化总运行时间的3%)也许你可以通过更好地利用推力来产生更大的影响。
建议:
device_data
数组,标记边界。这可能会对您的内核产生明显的改善。但同样,优化3%并不一定是您想要花费大量精力的地方。pos_ptr
数组后,请注意除此之外
inf
值已经排序。所以也许有一个更聪明的人
选项比“thrust :: sort”然后修剪数组,到
产生结果。看看像这样的推力函数
remove_if
和copy_if
。我没有对它进行基准测试,但我的猜测
他们的价格会明显低于排序,其次是
修剪数组(删除inf值)。