我有一个关于基于扭曲的并行缩减的想法,因为根据定义,扭曲的所有线程都是同步的。
因此,我们的想法是输入数据可以减少64倍(每个线程减少两个元素),而无需任何同步。
与马克·哈里斯(Mark Harris)的原始实现相同,减少量应用于块级,数据位于共享内存中。 http://gpgpu.org/static/sc2007/SC07_CUDA_5_Optimization_Harris.pdf
我创建了一个内核来测试他的版本和基于warp的版本。内核本身完全相同地将BLOCK_SIZE元素存储在共享内存中,并在输出数组的唯一块索引处输出其结果。
该算法本身可以正常工作。经过全面测试,以测试“计数”。
实现的功能主体:
/** * Performs a parallel reduction with operator add * on the given array and writes the result with the thread 0 * to the given target value * * @param inValues T* Input float array, length must be a multiple of 2 and equal to blockDim.x * @param targetValue float */ __device__ void reductionAddBlockThread_f(float* inValues, float &outTargetVar) { // code of the below functions }
1.执行他的版本:
if (blockDim.x >= 1024 && threadIdx.x < 512) inValues[threadIdx.x] += inValues[threadIdx.x + 512]; __syncthreads(); if (blockDim.x >= 512 && threadIdx.x < 256) inValues[threadIdx.x] += inValues[threadIdx.x + 256]; __syncthreads(); if (blockDim.x >= 256 && threadIdx.x < 128) inValues[threadIdx.x] += inValues[threadIdx.x + 128]; __syncthreads(); if (blockDim.x >= 128 && threadIdx.x < 64) inValues[threadIdx.x] += inValues[threadIdx.x + 64]; __syncthreads(); //unroll last warp no sync needed if (threadIdx.x < 32) { if (blockDim.x >= 64) inValues[threadIdx.x] += inValues[threadIdx.x + 32]; if (blockDim.x >= 32) inValues[threadIdx.x] += inValues[threadIdx.x + 16]; if (blockDim.x >= 16) inValues[threadIdx.x] += inValues[threadIdx.x + 8]; if (blockDim.x >= 8) inValues[threadIdx.x] += inValues[threadIdx.x + 4]; if (blockDim.x >= 4) inValues[threadIdx.x] += inValues[threadIdx.x + 2]; if (blockDim.x >= 2) inValues[threadIdx.x] += inValues[threadIdx.x + 1]; //set final value if (threadIdx.x == 0) outTargetVar = inValues[0]; }
资源:
使用4个同步线程12个if语句使用11个读取+添加+写操作1个最终写操作5个寄存器使用
性能:
五次测试平均运行时间:〜19.54毫秒
2.基于扭曲的方法:(与上面的功能相同)
/* * Perform first warp based reduction by factor of 64 * * 32 Threads per Warp -> LOG2(32) = 5 * * 1024 Threads / 32 Threads per Warp = 32 warps * 2 elements compared per thread -> 32 * 2 = 64 elements per warp * * 1024 Threads/elements divided by 64 = 16 * * Only half the warps/threads are active */ if (threadIdx.x < blockDim.x >> 1) { const unsigned int warpId = threadIdx.x >> 5; // alternative threadIdx.x & 31 const unsigned int threadWarpId = threadIdx.x - (warpId << 5); const unsigned int threadWarpOffset = (warpId << 6) + threadWarpId; inValues[threadWarpOffset] += inValues[threadWarpOffset + 32]; inValues[threadWarpOffset] += inValues[threadWarpOffset + 16]; inValues[threadWarpOffset] += inValues[threadWarpOffset + 8]; inValues[threadWarpOffset] += inValues[threadWarpOffset + 4]; inValues[threadWarpOffset] += inValues[threadWarpOffset + 2]; inValues[threadWarpOffset] += inValues[threadWarpOffset + 1]; } // synchronize all warps - the local warp result is stored // at the index of the warp equals the first thread of the warp __syncthreads(); // use first warp to reduce the 16 warp results to the final one if (threadIdx.x < 8) { // get first element of a warp const unsigned int warpIdx = threadIdx.x << 6; if (blockDim.x >= 1024) inValues[warpIdx] += inValues[warpIdx + 512]; if (blockDim.x >= 512) inValues[warpIdx] += inValues[warpIdx + 256]; if (blockDim.x >= 256) inValues[warpIdx] += inValues[warpIdx + 128]; if (blockDim.x >= 128) inValues[warpIdx] += inValues[warpIdx + 64]; //set final value if (threadIdx.x == 0) outTargetVar = inValues[0]; }
使用1个同步线程7个if语句10个读添加写操作1个最终写操作5个寄存器使用
5位移位1加1子
五次测试运行平均值:〜20.82毫秒
在带有256 mb浮点值的Geforce 8800 GT 512 mb上多次测试两个内核。并以每块256个线程(100%的占用率)运行内核。
基于经线的版本要慢〜1.28毫秒。
如果将来的卡允许更大的块大小,则基于扭曲的方法仍然不需要进一步的同步语句,因为最大值为4096,该数量减少为64,最终扭曲为1
为什么它不快?或者内核的缺点在哪里?
从资源使用情况来看,翘曲方法应该领先吗?
Edit1:更正了只有一半线程处于活动状态而不导致读取超出范围的内核,添加了新的性能数据