CUDA:将`+ =`并行应用于设备上的数组元素是否安全?

时间:2014-08-22 15:04:55

标签: c cuda

在编译和执行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之间的随机整数!我完全被这种行为所困惑。

1 个答案:

答案 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方法。