我已阅读Shuffle Tips and Tricks论文,但我不确定如何将其应用于我继承的一些狡猾的代码:
extern __shared__ unsigned int lpSharedMem[];
int tid = threadIdx.x;
lpSharedMem[tid] = startValue;
volatile unsigned int *srt = lpSharedMem;
// ...various stuff
srt[tid] = min( srt[tid], srt[tid+32] );
srt[tid] = min( srt[tid], srt[tid+16] );
srt[tid] = min( srt[tid], srt[tid+8] );
srt[tid] = min( srt[tid], srt[tid+4] );
srt[tid] = min( srt[tid], srt[tid+2] );
srt[tid] = min( srt[tid], srt[tid+1] );
__syncthreads();
即使没有CUDA,这段代码也很狡猾,但看着this implementation我看到了:
__device__ inline int min_warp(int val) {
val = min(val, __shfl_xor(val, 16));
val = min(val, __shfl_xor(val, 8));
val = min(val, __shfl_xor(val, 4));
val = min(val, __shfl_xor(val, 2));
val = min(val, __shfl_xor(val, 1));
return __shfl(val, 0);
}
此代码可以通过以下方式调用:
int minVal = min_warp(startValue);
因此,我可以用上面的代码替换我相当狡猾的volatile
。但是,我真的不明白发生了什么;有人可以解释我是否正确,以及min_warp()
函数究竟发生了什么。
答案 0 :(得分:8)
来自int __shfl_xor(int var, int laneMask, int width=warpSize);
的说明:
__ shfl_xor()通过使用laneMask执行调用者的通道ID的按位异或来计算源行ID:返回由结果通道ID保存的var的值。 (...)
通道ID是warp中线程的索引,从0到31.因此硬件为每个线程执行按位XOR:sourceLaneId XOR laneMask => destinationLaneId
例如,使用线程0和:
__shfl_xor(val, 16)
laneMask = 0b00000000000000000000000000010000 = 16(十进制)
srclaneID = 0b00000000000000000000000000000000 = 0(十进制)
XOR ---------------------------------------------- ------------
dstLaneID = 0b00000000000000000000000000010000 = 16(十进制)
然后线程0获取线程16的值。
现在使用主题4:
laneMask = 0b00000000000000000000000000010000 = 16(十进制)
srclaneID = 0b00000000000000000000000000000100 = 4(十进制)
XOR ---------------------------------------------- ------------
dstLaneID = 0b00000000000000000000000000010100 = 20(十进制)
所以线程4得到线程20的值。等等......
如果我们回到实际算法,我们会发现它是一个并行缩减,其中应用了min
运算符。在步骤:
PD:请注意这两个代码并不完全相同。这个'32'的偏移告诉我们你的共享内存数组是2 * WARP长。 (您将2 * WARP值减少为1)
srt[tid] = min( srt[tid], srt[tid+32] );
随机播放将WARP值降低为1。
答案 1 :(得分:1)
这些幻灯片第27页有一个直观的图表:
xor 1首先交换奇数/偶数单输入
xor 2然后交换奇数/偶数对先前交换的输入
xor 4然后交换奇数/偶数四元组
等
另一种方法是计算一个完整的xor掩码和xor原始输入值。使用不同的warp shuffle指令可以通过多种方式实现此目的。这种xor模式也用于FFT / DFT变换,也称为“蝶形图”。