【问题标题】:cuda shared memory - inconsistent resultscuda 共享内存 - 结果不一致
【发布时间】:2014-12-01 14:33:34
【问题描述】:

我正在尝试进行并行归约以对 CUDA 中的数组求和。目前我传递一个数组来存储每个块中元素的总和。这是我的代码:

#include <cstdlib>
#include <iostream>
#include <cuda.h>
#include <cuda_runtime_api.h>
#include <helper_cuda.h>
#include <host_config.h>
#define THREADS_PER_BLOCK 256
#define CUDA_ERROR_CHECK(ans) { gpuAssert((ans), __FILE__, __LINE__); }

using namespace std;

inline void gpuAssert(cudaError_t code, char *file, int line, bool abort=true)
{
   if (code != cudaSuccess) 
   {
      fprintf(stderr,"GPUassert: %s %s %d\n", cudaGetErrorString(code), file, line);
      if (abort) exit(code);
   }
}

struct double3c {
    double x; 
    double y;
    double z;
    __host__ __device__ double3c() : x(0), y(0), z(0) {}
    __host__ __device__ double3c(int x_, int y_, int z_) : x(x_), y(y_), z(z_) {}
    __host__ __device__ double3c& operator+=(const double3c& rhs) { x += rhs.x; y += rhs.y; z += rhs.z;}
    __host__ __device__ double3c& operator/=(const double& rhs) { x /= rhs; y /= rhs; z /= rhs;}

};

class VectorField {
public:
    double3c *data;
    int size_x, size_y, size_z;
    bool is_copy;  

    __host__ VectorField () {}

    __host__ VectorField (int x, int y, int z) {
        size_x = x; size_y = y; size_z = z;
        is_copy = false;
        CUDA_ERROR_CHECK (cudaMalloc(&data, x * y * z * sizeof(double3c))); 
    }

    __host__ VectorField (const VectorField& other) {
        size_x = other.size_x; size_y = other.size_y; size_z = other.size_z;
        this->data = other.data;
        is_copy = true;
    }

    __host__ ~VectorField() {     
        if (!is_copy) CUDA_ERROR_CHECK (cudaFree(data));
    }
};

__global__ void KernelCalculateMeanFieldBlock (VectorField m, double3c* result) {
    __shared__ double3c blockmean[THREADS_PER_BLOCK];    
    int index = threadIdx.x + blockIdx.x * blockDim.x;
    if (index < m.size_x * m.size_y * m.size_z) blockmean[threadIdx.x] = m.data[index] = double3c(0, 1, 0);
    else blockmean[threadIdx.x] = double3c(0,0,0);
    __syncthreads();
    for(int s = THREADS_PER_BLOCK / 2; s > 0; s /= 2) {
        if (threadIdx.x < s) blockmean[threadIdx.x] += blockmean[threadIdx.x + s];
        __syncthreads();
    }


    if(threadIdx.x == 0) result[blockIdx.x] = blockmean[0];   
}

double3c CalculateMeanField (VectorField& m) { 
    int blocknum = (m.size_x * m.size_y * m.size_z - 1) / THREADS_PER_BLOCK + 1;
    double3c *mean = new double3c[blocknum]();
    double3c *cu_mean;
    CUDA_ERROR_CHECK (cudaMalloc(&cu_mean, sizeof(double3c) * blocknum));
    CUDA_ERROR_CHECK (cudaMemset (cu_mean, 0, sizeof(double3c) * blocknum));

        KernelCalculateMeanFieldBlock <<<blocknum, THREADS_PER_BLOCK>>> (m, cu_mean);
        CUDA_ERROR_CHECK (cudaPeekAtLastError());
        CUDA_ERROR_CHECK (cudaDeviceSynchronize());
        CUDA_ERROR_CHECK (cudaMemcpy(mean, cu_mean, sizeof(double3c) * blocknum, cudaMemcpyDeviceToHost));

    CUDA_ERROR_CHECK (cudaFree(cu_mean));
    for (int i = 1; i < blocknum; i++) {mean[0] += mean[i];}
    mean[0] /= m.size_x * m.size_y * m.size_z;
    double3c aux = mean[0];
    delete[] mean;
    return aux;
}



int main() {
    VectorField m(100,100,100);
    double3c sum = CalculateMeanField (m);
    cout <<  sum.x << '\t' << sum.y << '\t' <<sum.z;  


    return 0;
}

编辑

贴出功能代码。用 10x10x10 元素构造 VectorField 可以正常工作并给出平均值 1,但用 100x100x100 元素构造它的平均值约为 0.97(它因运行而异)。这是进行并行缩减的正确方法,还是我应该坚持每个块启动一个内核?

【问题讨论】:

  • 您应该提供完整的代码。 m 是否已在设备上分配?在计算平均值之前,您需要在主机代码中使用一个循环来将每个块的结果相加(即将mean 的blocknum 元素相加到mean[0])。
  • 我有循环,但未能在问题中复制它。是的,m 是在设备上分配的。编辑问题
  • 请提供完整的MCVE。其他人可以复制、粘贴、编译和运行并观察问题的代码,无需添加任何内容或更改任何内容。 VectorField 的构造函数正在对类成员(属性)m 执行 cudaMalloc,但您在传递给设备的类中引用了另一个类成员 data。我宁愿不玩 20 个问题来尝试梳理这一切。请提供 MCVE。所以expects that.

标签: c++ memory cuda shared


【解决方案1】:

当我在 linux 上编译你现在拥有的代码时,我收到以下警告:

t614.cu(55): warning: __shared__ memory variable with non-empty constructor or destructor (potential race between threads)

不应忽略此类警告。它与这行代码相关联:

__shared__ double3c blockmean[THREADS_PER_BLOCK]; 

由于存储在共享内存中的这些对象的初始化(由构造函数)将以某种任意顺序发生,并且您在初始化这些对象与也将设置这些值的后续代码之间没有障碍,不可预测的事情 em> (*) 可能发生。

如果我在代码中插入__syncthreads() 以将构造函数活动与后续代码隔离,我会得到预期的结果:

__shared__ double3c blockmean[THREADS_PER_BLOCK];    
int index = threadIdx.x + blockIdx.x * blockDim.x;
__syncthreads();  // add this line
if (index < m.size_x * m.size_y * m.size_z) blockmean[threadIdx.x] = m.data[index] = double3c(0, 1, 0);
else blockmean[threadIdx.x] = double3c(0,0,0);
__syncthreads();

然而,这仍然给我们留下了警告。解决此问题并使警告消失的修改是动态分配必要的__shared__ 大小。将您的共享内存声明更改为:

extern __shared__ double3c blockmean[];

并修改您的内核调用:

KernelCalculateMeanFieldBlock <<<blocknum, THREADS_PER_BLOCK, THREADS_PER_BLOCK*sizeof(double3c)>>> (m, cu_mean);

这将消除警告,产生正确的结果,并避免共享内存变量上不必要的构造函数流量。 (并且不再需要上述额外的__syncthreads()。)

*关于“不可预测的事情”,如果您通过检查生成的 SASS (cuobjdump -sass ...) 或 PTX (**) (nvcc -ptx ...),您将看到 每个线程 将 整个 __shared__ 对象数组初始化为零(默认构造函数的行为)。因此,一些线程(即warp)可以提前并开始根据以下行填充共享内存区域:

if (index < m.size_x * m.size_y * m.size_z) blockmean[threadIdx.x] = m.data[index] = double3c(0, 1, 0);

然后,当其他 warp 开始执行时,这些线程将再次清除 整个 共享内存数组。这种竞速行为会导致不可预知的结果。

** 我通常不建议通过检查 PTX 来判断代码行为,但在这种情况下它同样具有指导意义。最终的编译阶段不会优化构造函数的行为。

【讨论】:

  • 如果共享内存在内核之外(但显然在同一个 cu 文件中),你会在哪里同步?
  • 我不确定我是否理解这个问题。 __syncthreads() 通常在填充共享内存的代码之后使用。
猜你喜欢
  • 2011-06-29
  • 2012-07-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多