无法在CUDA中找到1到100个数字的简单总和?

时间:2013-11-13 05:47:33

标签: c++ cuda

我正在使用CUDA处理图像处理算法。在我的算法中,我想使用CUDA内核找到所有图像像素的总和。所以我在cuda中制作了16位灰度图像的所有像素的测量和的核方法,但是得到了错误的答案。 所以我在cuda中制作简单的程序,找到1到100个数字的总和,我的代码如下。 在我的代码中,我没有得到使用GPU的1到100个数字的精确总和,但我得到了使用CPU的1到100个数字的精确总和。那么我在那段代码中做了什么?

#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <stdio.h>
#include <conio.h>
#include <malloc.h>
#include <limits>
#include <math.h>

using namespace std;

__global__ void computeMeanValue1(double *pixels,double *sum){

        int x = threadIdx.x;

        sum[0] = sum[0] + (pixels[(x)]);
        __syncthreads();
}

int main(int argc, char **argv)
{
    double *data;
    double *dev_data;
    double *dev_total;
    double *total;

    data=new double[(100) * sizeof(double)];
    total=new double[(1) * sizeof(double)];

    double cpuSum=0.0;

    for(int i=0;i<100;i++){
        data[i]=i+1;
        cpuSum=cpuSum+data[i];
    }
    cout<<"CPU total = "<<cpuSum<<std::endl;

    cudaMalloc( (void**)&dev_data, 100 * sizeof(double));
    cudaMalloc( (void**)&dev_total, 1 * sizeof(double));

    cudaMemcpy(dev_data, data, 100 * sizeof(double), cudaMemcpyHostToDevice);

    computeMeanValue1<<<1,100>>>(dev_data,dev_total);
    cudaDeviceSynchronize();

    cudaMemcpy(total, dev_total, 1* sizeof(double), cudaMemcpyDeviceToHost);
    cout<<"GPU total = "<<total[0]<<std::endl;

    cudaFree(dev_data);
    cudaFree(dev_total);

    free(data);
    free(total);

    getch();
    return 0;
}

2 个答案:

答案 0 :(得分:3)

所有线程都在同一时间写入同一个内存位置。

sum[0] = sum[0] + (pixels[(x)]);

您不能这样做,并希望得到正确的结果。您的内核需要采用不同的方法来避免从不同的线程写入相同的内存。通常用于这样做的模式是减少。简单地说,减少每个线程负责对数组中的元素块求和然后存储结果。通过采用一系列这些缩减操作,可以对数组的全部内容求和。

__global__ void block_sum(const float *input,
                          float *per_block_results,
                          const size_t n)
{
  extern __shared__ float sdata[];
  unsigned int i = blockIdx.x * blockDim.x + threadIdx.x;

  // load input into __shared__ memory
  float x = 0;
  if(i < n)
  {
    x = input[i];
  }
  sdata[threadIdx.x] = x;
  __syncthreads();

  // contiguous range pattern
  for(int offset = blockDim.x / 2;
      offset > 0;
      offset >>= 1)
  {
    if(threadIdx.x < offset)
    {
      // add a partial sum upstream to our own
      sdata[threadIdx.x] += sdata[threadIdx.x + offset];
    }

    // wait until all threads in the block have
    // updated their partial sums
    __syncthreads();
  }

  // thread 0 writes the final result
  if(threadIdx.x == 0)
  {
    per_block_results[blockIdx.x] = sdata[0];
  }
}

每个帖子写入sdata[threadIdx.x]中的其他位置,没有竞争条件。线程可以自由访问sdata中的其他元素,因为它们只读取它们,因此没有竞争条件。请注意使用__syncthreads()确保在线程开始读取数据之前完成将数据加载到sdata的操作以及第二次调用__syncthreads()以确保所有求和操作在复制sdata[0]的最终结果之前已完成。请注意,只有线程0将其结果写入per_block_results[blockIdx.x],因此也没有竞争条件。

您可以在Google Code找到上面的完整示例代码(我没有写这个)。此幻灯片具有reductions in CUDA的合理摘要。它包含的图表确实有助于理解交错存储器读写如何相互冲突。

您可以找到许多关于有效实现GPU减少的其他材料。确保您的实现最有效地利用内存是获得内存绑定操作(如还原)的最佳性能的关键。

答案 1 :(得分:1)

在GPU代码中,我们有多个并行执行的线程。如果所有这些线程都尝试更新内存中的相同位置,则我们会有未定义的行为,除非我们使用名为atomics的特殊操作来执行更新。

在您的情况下,由于sum由所有主题更新,而sumdouble数量,我们可以使用special custom atomic function described in the programming guide来完成此操作。

如果我用以下代码替换你的内核代码:

__device__ double atomicAdd(double* address, double val)
{
    unsigned long long int* address_as_ull =
                              (unsigned long long int*)address;
    unsigned long long int old = *address_as_ull, assumed;
    do {
        assumed = old;
        old = atomicCAS(address_as_ull, assumed,
                        __double_as_longlong(val +
                               __longlong_as_double(assumed)));
    } while (assumed != old);
    return __longlong_as_double(old);
}

__global__ void computeMeanValue1(double *pixels,double *sum){

        int x = threadIdx.x;
        atomicAdd(sum, pixels[x]);
}

在内核之前将sum值初始化为零:

double gpuSum = 0.0;
cudaMemcpy(dev_total, &gpuSum, sizeof(double), cudaMemcpyHostToDevice);

然后我想你会得到相应的结果。

正如@AdeMiller指出的那样,执行这样的并行求和的更快方法是通过经典的并行缩减。

有一个涵盖该方法的demonstrates thisaccompanying presentation的CUDA示例代码。