当多个线程写入时,在全局设备内存中获取意外值

时间:2017-03-10 08:23:03

标签: cuda gpgpu race-condition atomicity

这是cuda线程的问题,记忆魔法,它返回单线程结果" 100"但是会期望9个线程结果" 900"。

#indudel <stdio.h>
#include <assert.h>
#include <cuda_runtime.h>
#include <helper_functions.h>
#include <helper_cuda.h>


__global__ 
void test(int in1,int*ptr){
    int e = 0;

    for (int i = 0; i < 100; i++){
       e++;
    }

    *ptr +=e;

}


int main(int argc, char **argv)
{
   int devID = 0;


    cudaError_t error;
    error = cudaGetDevice(&devID);


    if (error == cudaSuccess)
    {
        printf("GPU Device fine\n");
    }
    else{
        printf("GPU Device problem, aborting");
        abort();
    }


    int* d_A;
    cudaMalloc(&d_A, sizeof(int));

    int res=0;

    //cudaMemcpy(d_A, &res, sizeof(int), cudaMemcpyHostToDevice);

    test <<<3, 3 >>>(0,d_A);

    cudaDeviceSynchronize();

    cudaMemcpy(&res, d_A, sizeof(int),cudaMemcpyDeviceToHost);

    printf("res is : %i",res);

     Sleep(10000);
     return 0;
}

它返回: GPU设备很好\ n res是:100

预计它会返回更高的数字,因为3x3(块,线程),只有一个线程的结果? 做错了什么,丢失的数字在哪里?

2 个答案:

答案 0 :(得分:1)

您不能以这种方式将总和写入全局记忆。 您必须使用atomic function来确保存储是原子的。

通常,当多个设备线程在全局内存中写入相同的值时,您必须使用atomic functions

  

float atomicAdd(float * address,float val);   double atomicAdd(double *   地址,双重val);

     

读取位于地址的32位或64位字   全局或共享内存,计算(旧+ val),并存储结果   回到同一地址的内存。这三个操作是   在一个原子事务中执行。该函数返回旧的。

thread synchronization

  

__syncthreads()的吞吐量是每个时钟周期16次操作   计算能力2.x的设备,每个时钟周期128次操作   计算能力3.x的设备,每个时钟周期32次操作   计算能力的设备每个时钟周期6.0和64次操作   对于计算能力为5.x,6.1和6.2的设备。

     

请注意__syncthreads()可以通过强制执行来影响性能   多处理器空闲,如设备内存访问中所述。

答案 1 :(得分:1)

(改编我的另一个answer:)

您正在体验增量运算符不是原子的效果。 (C++-oriented description of what that means)。按时间顺序排列的事件是以下事件序列(不一定按照线程的顺序排列):

  

...(其他工作)......

     

块0线程0向寄存器r发出带有地址ptr的LOAD指令   块0线程1向寄存器r发出带有地址ptr的LOAD指令   ...
  块2线程0向寄存器r发出带有地址ptr的LOAD指令

     


     

块0线程0完成LOAD,现在寄存器r中为0   ...
  块2线程2完成LOAD,现在寄存器r中有0

     


     

块0线程0将r加100   ...
  块2线程2将100添加到r

     


     

块0线程0从寄存器r发出STORE指令到地址ptr
  ...
  块2线程2从寄存器r发出STORE指令到地址ptr

因此每个线程都看到*ptr的初始值,即0;增加100;和商店0 + 100 = 100回来。只要所有线程都试图存储相同的假值,商店的顺序就不重要了。

您需要做的是:

  • 使用原子操作 - 对代码进行的修改量最少,但效率非常低,因为它会在很大程度上序列化您的工作,或者
  • 使用块级缩减原语。这将确保计算活动与共享块内存的部分排序 - 使用__syncthreads()或其他机制。因此,它可能首先让每个线程添加自己的两个元素;然后同步块线程;然后有更少的线程加上成对的对,等等。这是nVIDIA blog post关于在更现代的GPU架构上实现快速缩减的问题。

阻止本地或warp-local和/或特定于工作组的部分结果,这需要更少/更便宜的同步,并在完成大量工作后最终将它们组合在一起。< / p>