有人可以帮助我理解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错误的方法。有人可以纠正我吗?请!
答案 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 = 1024
和BLOCK_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 元素。