我正在尝试理解书中提到的扫描实现scan-then-fan:CUDA手册。
scanWarp
吗?为何负面指数?你能提一个数值例子吗?warpPartials[16+warpid] = sum
行提出了同样的问题。作业如何发生?if ( warpid==0 ) {scanWarp<T,bZeroPadded>( 16+warpPartials+tid ); }
sum += warpPartials[16+warpid-1];
吗?数值例子将受到高度赞赏。 *sPartials = sum;
中用于在sPartials
中存储值的索引? PS:演示整个执行的数字示例非常有用。
template < class T, bool bZeroPadded >
inline __device__ T
scanBlock( volatile T *sPartials ){
extern __shared__ T warpPartials[];
const int tid = threadIdx.x;
const int lane = tid & 31;
const int warpid = tid >> 5;
//
// Compute this thread's partial sum
//
T sum = scanWarp<T,bZeroPadded>( sPartials );
__syncthreads();
//
// Write each warp's reduction to shared memory
//
if ( lane == 31 ) {
warpPartials[16+warpid] = sum;
}
__syncthreads();
//
// Have one warp scan reductions
//
if ( warpid==0 ) {
scanWarp<T,bZeroPadded>( 16+warpPartials+tid );
}
__syncthreads();
//
// Fan out the exclusive scan element (obtained
// by the conditional and the decrement by 1)
// to this warp's pending output
//
if ( warpid > 0 ) {
sum += warpPartials[16+warpid-1];
}
__syncthreads();
//
// Write this thread's scan output
//
*sPartials = sum;
__syncthreads();
//
// The return value will only be used by caller if it
// contains the spine value (i.e. the reduction
// of the array we just scanned).
//
return sum;
}
template < class T >
inline __device__ T
scanWarp( volatile T *sPartials ){
const int tid = threadIdx.x;
const int lane = tid & 31;
if ( lane >= 1 ) sPartials[0] += sPartials[- 1];
if ( lane >= 2 ) sPartials[0] += sPartials[- 2];
if ( lane >= 4 ) sPartials[0] += sPartials[- 4];
if ( lane >= 8 ) sPartials[0] += sPartials[- 8];
if ( lane >= 16 ) sPartials[0] += sPartials[-16];
return sPartials[0];
}
答案 0 :(得分:3)
扫描然后风扇策略应用于两个级别。对于网格级扫描(对全局内存进行操作),将部分写入主机代码中分配的临时全局内存缓冲区,通过递归调用主机函数进行扫描,然后通过单独的内核调用添加到最终输出。对于块级扫描(在共享内存上运行),将部分写入共享内存(warpPartials[]
)的基础,由一个warp扫描,然后添加到块级扫描的最终输出。您要问的代码是进行块级扫描。
1)您正在引用的scanWarp
的实现是使用已添加threadIdx.x
的共享内存指针调用的,因此每个线程的sPartials
版本指向一个不同的共享内存元素。在sPartials
上使用固定索引会导致相邻线程在相邻的共享内存元素上运行。负指数是可以的,只要它们不会导致越界数组索引。这个实现借鉴了用零填充共享内存的优化版本,因此每个线程都可以无条件地使用固定的负索引,并且某个索引下面的线程只读取零。 (代码清单13.14)它可以很容易地在warp中的最低线程上执行谓词执行并使用正索引。
2)每个32线程warp的第31个线程包含warp的部分和,它必须存储在某处以便进行扫描然后添加到输出中。 warpPartials[]
别名来自第一个元素的共享内存,因此可用于保存每个warp的部分和。您可以使用共享内存的任何部分来执行此计算,因为每个线程在寄存器中都有自己的扫描值(赋值T sum = scanWarp...
)。
3)有些warp(可能是任何warp,因此也可能是warp 0)必须扫描写入warpPartials[]
的部分。最多需要一个warp,因为每个块有1024个线程的硬件限制= 1024/32或32个warp。所以这段代码利用了每个warp的最大线程数除以warp数不一定大于每个warp的最大线程数的巧合。
4)此代码将扫描的每个warp部分添加到每个输出元素。第一个warp已经具有正确的值,因此仅通过第二个和后续的warp进行添加。另一种看待它的方法是它将warp partials的独占扫描添加到输出中。
5)scanBlock
是一个设备函数 - 地址算术由其调用者scanAndWritePartials
完成:volatile T *myShared = sPartials+tid;
答案 1 :(得分:1)
(现在重写了我有更多时间)
这是一个例子(基于我在C ++ AMP中编写的实现,而不是CUDA)。为了使图表更小,每个warp是4个元素宽,块是16个元素。
以下文章也很有用Efficient Parallel Scan Algorithms for GPUs。和Parallel Scan for Stream Architectures一样。