CUDA/推力双指针问题(指针的矢量)

  • 本文关键字:指针 问题 CUDA cuda thrust
  • 更新时间 :
  • 英文 :


大家好,我正在使用CUDA和Thrust库。当我试图访问CUDA内核上的双指针时,遇到了一个问题,该内核加载了来自主机的Object*(指针向量)类型的thrust::device_vector。当使用"nvcc-o rust main.cpp cukernel.cu"编译时,我在尝试运行程序时收到警告"警告:假定全局内存空间,无法判断指针指向什么"和启动错误。

我读过英伟达论坛,解决方案似乎是"不要在CUDA内核中使用双指针"。我不想在发送到内核之前将双指针折叠成1D指针。。。有人找到解决这个问题的办法了吗?所需代码如下,提前感谢!

--------------------------
        main.cpp
--------------------------
Sphere * parseSphere(int i)
{
  Sphere * s = new Sphere();
  s->a = 1+i;
  s->b = 2+i;
  s->c = 3+i;
  return s;
}
int main( int argc, char** argv ) {
  int i;
  thrust::host_vector<Sphere *> spheres_h;
  thrust::host_vector<Sphere> spheres_resh(NUM_OBJECTS);
  //initialize spheres_h
  for(i=0;i<NUM_OBJECTS;i++){
    Sphere * sphere = parseSphere(i);
    spheres_h.push_back(sphere);
  }
  //initialize spheres_resh
  for(i=0;i<NUM_OBJECTS;i++){
    spheres_resh[i].a = 1;
    spheres_resh[i].b = 1;
    spheres_resh[i].c = 1;
  }
  thrust::device_vector<Sphere *> spheres_dv = spheres_h;
  thrust::device_vector<Sphere> spheres_resv = spheres_resh;
  Sphere ** spheres_d = thrust::raw_pointer_cast(&spheres_dv[0]);
  Sphere * spheres_res = thrust::raw_pointer_cast(&spheres_resv[0]);
  kernelBegin(spheres_d,spheres_res,NUM_OBJECTS);
  thrust::copy(spheres_dv.begin(),spheres_dv.end(),spheres_h.begin());
  thrust::copy(spheres_resv.begin(),spheres_resv.end(),spheres_resh.begin());
  bool result = true;
  for(i=0;i<NUM_OBJECTS;i++){
    result &= (spheres_resh[i].a == i+1);
    result &= (spheres_resh[i].b == i+2);
    result &= (spheres_resh[i].c == i+3);
  }
  if(result)
  {
    cout << "Data GOOD!" << endl;
  }else{
    cout << "Data BAD!" << endl;
  }
  return 0;
}

--------------------------
        cukernel.cu
--------------------------
__global__ void deviceBegin(Sphere ** spheres_d, Sphere * spheres_res, float    
num_objects)
{
  int index = threadIdx.x + blockIdx.x*blockDim.x;
  spheres_res[index].a = (*(spheres_d+index))->a; //causes warning/launch error
  spheres_res[index].b = (*(spheres_d+index))->b; 
  spheres_res[index].c = (*(spheres_d+index))->c; 
}
void kernelBegin(Sphere ** spheres_d, Sphere * spheres_res, float num_objects)
{
 int threads = 512;//per block
 int grids = ((num_objects)/threads)+1;//blocks per grid
 deviceBegin<<<grids,threads>>>(spheres_d, spheres_res, num_objects);
}

这里的基本问题是设备向量spheres_dv包含主机指针。Thrust无法在GPU和主机CPU地址空间之间进行"深度复制"或指针转换。因此,当您将spheres_h复制到GPU内存时,您将得到一个主机指针的GPU阵列。GPU上主机指针的定向是非法的——它们是错误内存地址空间中的指针,因此你在内核中得到的GPU相当于segfault。

解决方案将涉及用在GPU上执行内存分配的东西来替换parseSphere函数,而不是使用parseSphere,后者目前在主机内存中分配每个新结构。如果你有一个费米GPU(看起来你没有),并且使用CUDA 3.2或4.0,那么一种方法是将parseSphere变成内核。设备代码中支持C++new运算符,因此结构创建将在设备内存中进行。您需要修改Sphere的定义,以便将构造函数定义为__device__函数,以便此方法能够工作。

另一种方法是创建一个包含设备指针的主机数组,然后将该数组复制到设备内存中。你可以在这个答案中看到一个例子。请注意,声明包含thrust::device_vectorthrust::device_vector可能不起作用,因此您可能需要使用底层CUDA API调用来构建设备指针数组。

你还应该注意到,我没有提到反向复制操作,这同样很难做到

底线是,推力(以及C++STL容器)实际上并不是用来容纳指针的。它们旨在保存值,并通过使用迭代器和底层算法抽象掉指针间接性和直接内存访问,而用户不应该看到这些。此外,"深度复制"问题是NVIDIA论坛上的智者反对GPU代码中多级指针的主要原因。它极大地使代码复杂化,而且在GPU上执行速度也较慢。