在编译和执行CUDA脚本后,我注意到了奇怪的(不正确的)行为,并且能够将其与以下最小示例隔离开来。首先,我为整数数组定义了一个export-to-CSV函数(只是为了方便调试):
#include <stdio.h>
#include <stdlib.h>
void int1DExportCSV(int *ptr, int n){
FILE *f;
f = fopen("1D IntOutput.CSV", "w");
int i = 0;
for (i = 0; i < n-1; i++){
fprintf(f, "%i,", ptr[i]);
}
fprintf(f, "%i", ptr[n-1]);
}
然后我定义了一个内核函数,它将输入数组的某个元素增加一个:
__global__ void kernel(int *ptr){
int x = blockIdx.x;
int y = blockIdx.y;
int offset = x + gridDim.x * y;
ptr[offset] += 1;
}
主循环分配一个名为a
的向量,分配一个空数组b
,并分配一个名为a
的{{1}}的设备副本:
dev_a
然后我将#define DIM 64
int main(void){
int *a;
a = (int*)malloc(DIM*DIM*sizeof(int));
int i;
for(i = 0; i < DIM*DIM; i++){
a[i] = 0;
}
int *b;
b = (int*)malloc(DIM*DIM*sizeof(int));
int *dev_a;
cudaMalloc( (void**)&dev_a, sizeof(int)*DIM*DIM );
cudaMemcpy( dev_a, a, DIM*DIM*sizeof(int), cudaMemcpyHostToDevice );
送入DIM-by-DIM-by-DIM网格块,每个网格都有DIM线程,将结果复制回来,然后将它们导出为CSV:
dev_a
生成的CSV文件长度为DIM * DIM,并填充了DIM。然而,虽然长度是正确的,但它应该用DIM * DIM填充,因为我基本上推出了DIM * DIM * DIM * DIM超线程的线程,其中最后两个维度都用于增加一个独特的元素。设备数组 dim3 blocks(DIM,DIM,DIM);
kernel<<<blocks,DIM>>>(dev_a);
cudaMemcpy( b, dev_a, sizeof(int)*DIM*DIM, cudaMemcpyDeviceToHost );
cudaFree(dev_a);
int1DExportCSV(b, DIM*DIM);
}
由一个。
我的第一反应是怀疑dev_a
步骤可能是罪魁祸首,因为多个线程可能在同一时间执行此步骤,因此每个线程可能在不知情的情况下更新ptr的旧副本有很多其他线程在同一时间做这件事。但是,我对CUDA的“禁忌”了解不足以判断这是否合理。
硬件问题(据我所知)不是问题;我使用的是GTX560 Ti,因此允许启动一个三维网格块,每块的线程数为64,远低于Fermi架构强加的最大值1024。
我犯了一个简单的错误吗?或者我的例子中有一个微妙的错误?
此外,我注意到当我将DIM增加到256时,生成的数组似乎填充了290到430之间的随机整数!我完全被这种行为所困惑。
答案 0 :(得分:3)
每个threadblock中的线程都在更新内存中的相同位置:
ptr[offset] += 1;
offset
对于块中的每个线程都是相同的:
int x = blockIdx.x;
int y = blockIdx.y;
int offset = x + gridDim.x * y;
这是禁忌。结果未定义。 而是使用atomics:
atomicAdd(ptr+offset, 1);
或某种parallel reduction方法。