在cuda中将3D数组展平为1D

时间:2017-05-09 22:23:44

标签: c++ arrays indexing 3d cuda

我有以下代码,我试图在cuda中实现,但我在cuda中将3D数组展平为1D的问题

C ++代码

for(int i=0; i<w; i++)
  for(int j=0; j<h; j++)
    for(int k=0; k<d; k++)
     arr[h*w*i+ w*j+ k] = (h*w*i+ w*j+ k)*2;

这就是我目前在Cuda的作品

  int w = h = d;
  int N = 64;

 __global__ void getIndex(float* A)
{
  int i = blockIdx.x;
  int j = blockIdx.y;
  int k = blockIdx.z;
  A[h*w*i+ w*j+ k] = h*w*i+ w*j+ k;
}


int main(int argc, char **argv)
 {

    float *d_A;
    cudaMalloc((void **)&d_A, w * h * d * sizeof(float) );
    getIndex <<<N,1>>> (d_A);
  }

但是我没有得到我期待的结果,我不知道如何获得正确的i,jk指数

1 个答案:

答案 0 :(得分:2)

考虑尺寸为w x h x d的3D问题。 (这可能是一个简单的数组,必须像您的问题或任何其他易于并行化的3D问题一样进行设置。)我将使用您的简单set-task进行演示。

使用CUDA内核处理此问题的最简单方法是为每个数组条目启动一个线程,即w*h*d个线程。 This answer讨论了为什么每个元素的一个线程可能并不总是最佳解决方案。

现在让我们看看下面的代码行

dim3 numThreads(w,h,d);
getIndex <<<1, numThreads>>> (d_A, w, h, d);

这里我们发布一个总共有w*h*d个线程的内核。 内核可以实现为

__global__ void getIndex(float* A, int w, int h, int d) // we actually do not need w
{
    int i = threadIdx.x;
    int j = threadIdx.y;
    int k = threadIdx.z;
    A[h*d*i+ d*j+ k] = h*d*i+ d*j+ k;
}

但是这个内核和内核调用存在一个问题:每个线程块的线程数是有限的(“特定方向的线程数”也是有界的= z方向通常是最有界的)。由于我们只调用一个线程块,因此我们的问题大小不能超过这些特定限制(例如w*h*d <= 1024)。

这就是线程块的用途。您几乎可以根据需要启动具有多个线程的内核。 (事实并非如此,但最大线程数量的限制可能不会用尽。)

以这种方式调用内核:

dim3 numBlocks(w/8,h/8,d/8);
dim3 numThreads(8,8,8);
getIndex <<<numBlocks, numThreads>>> (d_A, w, h, d);

将为w/8 * h/8 * d/8线程块启动内核,而每个块包含8*8*8个线程。因此,将调用总共w*h*d个线程。 现在我们必须相应地调整我们的内核:

__global__ void getIndex(float* A, int w, int h, int d) // we actually do not need w
{
    int bx = blockIdx.x;
    int by = blockIdx.y;
    int bz = blockIdx.z;
    int tx = threadIdx.x;
    int ty = threadIdx.y;
    int tz = threadIdx.z;
    A[h*d*(8*bx + tx)+ d*(8*by + ty)+ (8*bz + tz)] = h*d*(8*bx + tx)+ d*(8*by + ty)+ (8*bz + tz);
}

注意:

  • 您可以使用blockDim.x而不是固定大小8gridDim.x来编写更通用的内核,以通过w计算gridDim.x*blockDim.x。其他两个维度也同样处理。
  • 在提议的示例中,所有三个维度whd必须是8的倍数。您还可以将内核概括为允许每个维度。 (然后你必须解析内核的所有三个维度,以检查计算的位置是否仍在问题的范围内。)
  • 如前所述,每个线程编辑多个数组条目可能更有效。调用内核时必须再次考虑这一点。一个包装函数可以使用问题大小和数据并使用正确的块和线程配置调用内核可能很有用。