简单的CUDA代码提供意外的输出

时间:2015-12-06 22:28:12

标签: cuda

我是一个简单的CUDA内核,它将int的值递增1并将bool从true更改为false。这是我的代码。

#include <stdio.h>

__global__ void cube(int* d_var, bool* d_bool){
    int idx = threadIdx.x;
    //do basically nothing
    __syncthreads();
    *d_var = *d_var + 1;
    *d_bool = false;
}

int main(int argc, char** argv){

    int h_var = 1;
    int* d_var;
    bool h_bool = true;
    bool* d_bool;

    cudaMalloc((void**)&d_var, sizeof(int));
    cudaMalloc((void**)&d_bool, sizeof(bool));

    while(h_var < 10){
        h_bool = true;
        //printf("%d\n", h_bool);
        //printf("%d\n", h_var);
        cudaMemcpy(d_var, &h_var, sizeof(int), cudaMemcpyHostToDevice);
        cudaMemcpy(d_bool, &h_bool, sizeof(bool), cudaMemcpyHostToDevice);
        cube<<<10, 512>>>(d_var, d_bool);
        cudaThreadSynchronize();
        cudaMemcpy(&h_var, d_var, sizeof(int), cudaMemcpyDeviceToHost);
        cudaMemcpy(&h_bool, d_bool, sizeof(bool), cudaMemcpyDeviceToHost);
        printf("%d\n", h_var);
        printf("%d\n", h_bool);

    }

    cudaFree(d_var);
    cudaFree(d_bool);

    //cudaFree(d_out);

    return 0;
}

问题是代替1,输出在每一步中显示增量为2。因此输出

1
3
5
7
11

有人可以帮我理解这里发生的事情。

1 个答案:

答案 0 :(得分:1)

你有竞争条件。与内核启动相关联的网格中的所有线程都在尝试更新相同的位置(d_var)。您必须正确管理此访问权限,否则您将获得不可预测的结果。

为了更好地了解竞争条件,你需要意识到这样的操作:

*d_var = *d_var + 1;

由机器在multiple steps执行。当多个线程在同一位置上异步执行这些多个步骤时,结果是不可预测的。一个线程可以覆盖另一个线程刚写入的内容,结果将不一致。

__syncthreads() doesn't do anything to manage multiple threads accessing the same location,此外__syncthreads仅对块内的线程进行操作,而不是对网格中的所有线程进行操作。

管理同时访问的一种可能方法是使用 atomics 。 Atomics将强制同时尝试这样做的多个线程有序地访问内存位置。

您可以像这样修改内核:

__global__ void cube(int* d_var, bool* d_bool){
    int idx = threadIdx.x;
    //do basically nothing
    __syncthreads();
    atomicAdd(d_var, 1);   // modify this line
    *d_bool = false;
}

现在,对于每次内核启动,这将导致d_var每个线程更新一次。因此,如果您启动单个线程(<<<1,1>>>),则每个内核启动时您的变量应增加一个。如果你启动了5120个线程(<<<10,512>>>),那么每个内核启动时你的变量应该增加5120个。

请注意,在这种情况下我们不需要担心d_bool,因为唯一可能的结果是它被设置为false,并且在这种情况下即使多个线程正在执行它也能得到保证。

如果你只希望每个内核启动时将变量增加1,无论网格中有多少个线程,那么你可以修改你的内核代码以仅在一个线程上调整更新:

__global__ void cube(int* d_var, bool* d_bool){
    int idx = threadIdx.x + blockDim.x*blockIdx.x;  // create globally unique thread ID
    //do basically nothing
    __syncthreads();
    if (idx == 0) // only thread 0 does the update
      *d_var = *d_var + 1;
    *d_bool = false;  // all threads will do this
}

通过这种修改,我得到了我认为的预期结果:

$ cat t997.cu
#include <stdio.h>

__global__ void cube(int* d_var, bool* d_bool){
    int idx = threadIdx.x + blockDim.x*blockIdx.x;
    //do basically nothing
    __syncthreads();
    if (idx == 0)
      *d_var = *d_var + 1;
    *d_bool = false;
}

int main(int argc, char** argv){

    int h_var = 1;
    int* d_var;
    bool h_bool = true;
    bool* d_bool;

    cudaMalloc((void**)&d_var, sizeof(int));
    cudaMalloc((void**)&d_bool, sizeof(bool));

    while(h_var < 10){
        h_bool = true;
        //printf("%d\n", h_bool);
        //printf("%d\n", h_var);
        cudaMemcpy(d_var, &h_var, sizeof(int), cudaMemcpyHostToDevice);
        cudaMemcpy(d_bool, &h_bool, sizeof(bool), cudaMemcpyHostToDevice);
        cube<<<10, 512>>>(d_var, d_bool);
        cudaThreadSynchronize();
        cudaMemcpy(&h_var, d_var, sizeof(int), cudaMemcpyDeviceToHost);
        cudaMemcpy(&h_bool, d_bool, sizeof(bool), cudaMemcpyDeviceToHost);
        printf("%d\n", h_var);
        printf("%d\n", h_bool);

    }

    cudaFree(d_var);
    cudaFree(d_bool);

    //cudaFree(d_out);

    return 0;
}
$ nvcc -o t997 t997.cu
$ ./t997
2
0
3
0
4
0
5
0
6
0
7
0
8
0
9
0
10
0
$

由于您的代码正在打印出h_bool变量,并且打印输出从2开始,而不是1,因为您的第一个打印输出是在内核执行后,因此存在零。