关于CUDA部分和码的混淆threadIdx.x,blockDim.x和a 2

时间:2013-01-12 07:19:58

标签: cuda sum nvidia

有人可以帮助我理解CUDA-C的部分并行和算法实现吗?我无法理解共享partialSum数组的初始填充[第3行到第8行]。我已经跟踪了好几个小时,但我不知道为什么应该从以下代码开始2*blockIdx.x*blockDim.x;而不是blockIdx.x*blockDim.x;

主机代码:

numOutputElements = numInputElements / (BLOCK_SIZE<<1);
 if (numInputElements % (BLOCK_SIZE<<1)) {
     numOutputElements++;
 }
#define BLOCK_SIZE 512
dim3 dimGrid(numOutputElements, 1, 1);
dim3 dimBlock(BLOCK_SIZE, 1, 1);
total<<<dimGrid, dimBlock>>>(deviceInput, deviceOutput, numInputElements);

内核代码:

1    __global__ void total(float * input, float * output, int len) {
2    
3    __shared__ float partialSum[2*BLOCK_SIZE];
4      
5      unsigned int t = threadIdx.x;
6      unsigned int start = 2*blockIdx.x*blockDim.x;
7      partialSum[t] = input[start + t];
8      partialSum[blockDim.x + t] = input[start + blockDim.x + t];
9     
10    for (unsigned int stride = blockDim.x; stride >=1; stride >>=1)
11      {
12       __syncthreads();
13        
14       if (t < stride)
15         partialSum[t] += partialSum[t + stride];
16      }
17      output[blockIdx.x] = partialSum[0];   
18  }

假设我有10个元素要求和,我选择使用4个块大小,每个块4个线程,所以将使用3个块,对吧? [让我们暂时忘记经线尺寸和东西]

当blockIdx.x为2(最后一个具有2个元素的块)时,开始变为(2 * 2 * 4 =)16并且它大于10且超过input长度(因此两者都是partialSum[t]partialSum[blockDim.x + t]将保持不变,block2的共享内存将保持为空。)如果是这样,那么我的数组的最后2个元素将会丢失!!

这让我觉得我得到的是blockIdx.x,blockDim.x错误的方法。有人可以纠正我吗?请!

2 个答案:

答案 0 :(得分:3)

你只能启动一半的块,并且每个块的工作量是你的两倍。这样做的好处是存储部分总和所需的临时空间减少了一半(因为你只发射了一半的块)。

通常的做缩减的方法(在这种情况下是总和),就是做这样的事情。

1    __global__ void total(float * input, float * output, int len) {
2    
3    __shared__ float partialSum[BLOCK_SIZE];
4      
5     unsigned int t = threadIdx.x;
6     unsigned int start = blockIdx.x*blockDim.x;
7     partialSum[t] = 0;
8     for (int T = start; T < len; T += blockDim.x * gridDim.x) 
9        partialSum[t] += input[T];
10    for (unsigned int stride = blockDim.x/2; stride >=1; stride >>=1)
11      {
12       __syncthreads();
13        
14       if (t < stride)
15         partialSum[t] += partialSum[t + stride];
16      }
17      output[blockIdx.x] = partialSum[0];   
18  }

因此,如果您有len = 1024BLOCK_SIZE = 256,则可以启动任何&lt; = 4个区块。

让我们看看第8行和第8行中包含的for循环中发生了什么。 9当你启动不同数量的块时。另外请记住输出需要有元素数量==块数。

  • Blocks == 4意味着,blockDim.x * gridDim.x = 256 x 4 = 1024,所以它只会迭代一次。输出的非合并写入次数= 4。
  • Blocks == 2意思是,blockDim.x * gridDim.x = 256 x 2 = 512,所以它会迭代两次。输出的非合并写入次数= 2。
  • Blocks == 1意味着,blockDim.x * gridDim.x = 256 x 1 = 256,所以它会迭代4次。输出的非合并写入次数= 1。

因此,启动更少的块可以减少内存占用并减少全局写入。然而,它降低了并行性。

理想情况下,您需要启发式地找到哪种组合最适合您的算法。或者您可以使用为您执行此操作的现有库。

有问题的内核选择启动一半的块以获得一些性能提升。但是可能不需要使用两倍的共享内存。

答案 1 :(得分:1)

您可能在计算块时遇到问题。

假设您有10个要求和的元素,并且您选择使用4个块大小和每个块4个线程,那么将只使用 TWO 块。

由于每个线程负责全局设备mem中的 TWO 元素,因此根据您的内核代码。

每个线程读取的输入元素如下所示。我没有在您的代码中看到任何范围检查。所以我假设你的10个元素有足够的零填充。

blockIdx.x           : 0 0 0 0  1 1 1 1  2 2 2 2  3 3 3 3
threadIdx.x          : 0 1 2 3  0 1 2 3  0 1 2 3  0 1 2 3
linear thread id     : 0 1 2 3  4 5 6 7  8 9 a b  c d e f

Idx of the element     0 1 2 3  8 9
read by the thread   : 4 5 6 7

因此output[0]存储elmemnt 0~7的总和,output[1]存储元素8~9的总和。我认为什么都不会丢失。

请参阅Optimizing Parallel Reduction in CUDA中的内核4,了解有关2*原因的性能问题。 @Pavan在他的回答中给出的较慢的内核3和内核是类似的实现,其中每个线程只负责 ONE 元素。

相关问题