CUDA-atomicAdd总计仅为16777216

CUDA-atomicAdd总计仅为16777216,cuda,Cuda,在运行以下内核时,我遇到了以下容易重现的问题,该内核除了原子添加浮点数外什么都不做: #define OUT_ITERATIONS 20000000 #define BLOCKS 12 #define THREADS 192 __global__ void testKernel(float* result) { int i = threadIdx.x + blockIdx.x * blockDim.x; float bias = 1.0f; int n = 1;

在运行以下内核时,我遇到了以下容易重现的问题,该内核除了原子添加浮点数外什么都不做:

#define OUT_ITERATIONS 20000000
#define BLOCKS 12
#define THREADS 192

__global__ void testKernel(float* result) {
    int i = threadIdx.x + blockIdx.x * blockDim.x;
    float bias = 1.0f;
    int n = 1;

    while (i < OUT_ITERATIONS) {
        atomicAdd(result, bias);
        i += BLOCKS * THREADS;
    }
}
#定义出2000万次迭代
#定义块12
#定义线程192
__全局无效测试内核(浮点*结果){
int i=threadIdx.x+blockIdx.x*blockDim.x;
浮动偏置=1.0f;
int n=1;
while(i
内核应该增加结果OUT_迭代次数,即20M。我使用以下标准代码调用内核:

int main() {
cudaError_t cudaStatus;
float* result;
float* dev_result;

// Choose which GPU to run on, change this on a multi-GPU system.
cudaStatus = cudaSetDevice(0);
if (cudaStatus != cudaSuccess) {
    fprintf(stderr, "cudaSetDevice failed!  Do you have a CUDA-capable GPU installed?");
    goto Error;
}

result = new float;
cudaStatus = cudaMalloc((void**)&dev_result, sizeof(float));
if (cudaStatus != cudaSuccess) {
    fprintf(stderr, "cudaMalloc failed: %s\n", cudaGetErrorString(cudaStatus));
    goto Error;
}
cudaStatus = cudaMemset(dev_result, 0, sizeof(float));
if (cudaStatus != cudaSuccess) {
    fprintf(stderr, "cudaMemset failed: %s\n", cudaGetErrorString(cudaStatus));
    goto Error;
}

// Launch a kernel on the GPU with one thread for each element.
testKernel<<<BLOCKS, THREADS>>>(dev_result);

// Check for any errors launching the kernel
cudaStatus = cudaGetLastError();
if (cudaStatus != cudaSuccess) {
    fprintf(stderr, "addKernel launch failed: %s\n", cudaGetErrorString(cudaStatus));
    goto Error;
}

// cudaDeviceSynchronize waits for the kernel to finish, and returns
// any errors encountered during the launch.
cudaStatus = cudaDeviceSynchronize();
if (cudaStatus != cudaSuccess) {
    fprintf(stderr, "cudaDeviceSynchronize returned error code %d after launching addKernel!\n", cudaStatus);
    goto Error;
}

cudaStatus = cudaMemcpy(result, dev_result, sizeof(float), cudaMemcpyDeviceToHost);
if (cudaStatus != cudaSuccess) {
    fprintf(stderr, "cudaMemcpy failed: %s\n", cudaGetErrorString(cudaStatus));
    goto Error;
}

printf("Result: %f\n", *result);
intmain(){
cudaError\u t cudaStatus;
浮动*结果;
浮动*偏差结果;
//选择要在哪个GPU上运行,在多GPU系统上更改此选项。
cudaStatus=cudaSetDevice(0);
if(cudaStatus!=cudaSuccess){
fprintf(stderr,“cudaSetDevice失败!是否安装了支持CUDA的GPU?”);
转到错误;
}
结果=新浮点数;
cudaStatus=cudamaloc((void**)和dev_结果,sizeof(float));
if(cudaStatus!=cudaSuccess){
fprintf(stderr,“cudamaloc失败:%s\n”,cudaGetErrorString(cudaStatus));
转到错误;
}
cudaStatus=cudaMemset(dev_结果,0,sizeof(float));
if(cudaStatus!=cudaSuccess){
fprintf(stderr,“cudaMemset失败:%s\n”,cudaGetErrorString(cudaStatus));
转到错误;
}
//在GPU上启动内核,每个元素有一个线程。
testKernel(dev_结果);
//检查启动内核时是否有任何错误
cudaStatus=cudaGetLastError();
if(cudaStatus!=cudaSuccess){
fprintf(stderr,“addKernel启动失败:%s\n”,cudaGetErrorString(cudaStatus));
转到错误;
}
//cudaDeviceSynchronize等待内核完成,然后返回
//在启动过程中遇到的任何错误。
cudaStatus=cudaDeviceSynchronize();
if(cudaStatus!=cudaSuccess){
fprintf(stderr,“cudaDeviceSynchronize在启动addKernel!\n后返回错误代码%d”,cudaStatus);
转到错误;
}
cudaStatus=cudaMemcpy(结果、开发结果、sizeof(浮点)、cudaMemcpyDeviceToHost);
if(cudaStatus!=cudaSuccess){
fprintf(stderr,“cudaMemcpy失败:%s\n”,cudaGetErrorString(cudaStatus));
转到错误;
}
printf(“结果:%f\n”,*结果);
但是,最后打印的结果是16777216.0,顺便说一句,十六进制为0x1000000。如果OUT_迭代次数<16777216,则不会出现问题,也就是说,如果我将其更改为16777000,例如,确保输出为16777000.0


系统:NVidia Titan、CUDA 5.5、Windows7

20M不符合
浮点中可用的整数精度

float
数量没有32位尾数(您通过观察“顺便说一句,十六进制中的0x1000000”发现有多少尾数位),因此它不能以
int
无符号int
的方式表示所有整数

16777216是可以可靠地存储在
浮点
中的最大整数

如果要可靠地将20M存储为整数,请将存储范围限制为适合
浮点
,或者使用其他表示形式,例如
无符号整数
双精度


这实际上不是CUDA的问题。在主机代码中,尝试将大整数存储在
浮点
中也会遇到类似的困难。

20M不符合
浮点
中可用的整数精度

float
数量没有32位尾数(您通过观察“顺便说一句,十六进制中的0x1000000”发现有多少尾数位),因此它不能以
int
无符号int
的方式表示所有整数

16777216是可以可靠地存储在
浮点
中的最大整数

如果要可靠地将20M存储为整数,请将存储范围限制为适合
浮点
,或者使用其他表示形式,例如
无符号整数
双精度


这实际上不是CUDA的问题。在主机代码中,尝试将大整数存储在
浮点
中也会遇到类似的困难。

此问题是由于类型
浮点
的精度有限

float
只有24位二进制精度。如果添加两个数字,其中一个大于另一个的
2^24-1
倍,结果将与较大的一个完全相同

当你把一个像16777216.0(=2^24)这样的大数字和一个像1.0这样的小数字相加时,你会失去一些精度,结果仍然是16777216.0。同样的情况也会发生在标准C程序中

float a=16777216.0f;
float b=1.0f;
printf("%f\n",a+b);
您可以用
double
int
替换
float
,以解决此问题

有关
atomicAdd()


此问题是由于类型
float
的精度有限造成的

float
只有24位二进制精度。如果添加两个数字,其中一个大于另一个的
2^24-1
倍,结果将与较大的一个完全相同

当你把一个像16777216.0(=2^24)这样的大数字和一个像1.0这样的小数字相加时,你会失去一些精度,结果仍然是16777216.0。同样的情况也会发生在标准C程序中

float a=16777216.0f;
float b=1.0f;
printf("%f\n",a+b);
您可以用
double
int
替换
float
,以解决此问题

有关
atomicAdd()


我真傻。非常感谢!双精度原子加法没有硬件支持。@Archaea cuda doc提供了一个使用atomicCAS()的实现,如上面的链接所示。@Archaea我认为您的链接和我的链接中的代码都提供了相同的函数,可以在原子上为现有双精度数据添加值。它们都使用atomicCAS()实施