试图理解前缀和执行

时间:2014-03-05 21:50:49

标签: c++ cuda

我正在尝试理解书中提到的扫描实现scan-then-fan:CUDA手册。

  1. 有人可以解释设备功能scanWarp吗?为何负面指数?你能提一个数值例子吗?
  2. 我对第warpPartials[16+warpid] = sum行提出了同样的问题。作业如何发生?
  3. 这一行if ( warpid==0 ) {scanWarp<T,bZeroPadded>( 16+warpPartials+tid ); }
  4. 的贡献是什么
  5. 你能解释一下sum += warpPartials[16+warpid-1];吗?数值例子将受到高度赞赏。
  6. 最后,更加面向c ++的问题我们如何知道*sPartials = sum;中用于在sPartials中存储值的索引?
  7. 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];
    }
    

2 个答案:

答案 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个元素。

enter image description here

以下文章也很有用Efficient Parallel Scan Algorithms for GPUs。和Parallel Scan for Stream Architectures一样。