你如何迭代一个倾斜的CUDA阵列?

时间:2014-06-19 00:17:08

标签: c++ arrays memory cuda

之前与OpenMP并行化,我试图绕过CUDA,这对我来说似乎不太直观。在这一点上,我试图准确理解如何以并行方式循环数组。

Cuda by Example是一个很好的开始。

第43页的代码段显示:

__global__ void add( int *a, int *b, int *c ) {
  int tid = blockIdx.x; // handle the data at this index
  if (tid < N)
     c[tid] = a[tid] + b[tid];
  }

在OpenMP中,程序员选择循环运行的次数,OpenMP将其分成线程,在CUDA中你必须告诉它(通过<<<...>>>中的块数和线程数)运行它足够的时间迭代数组,使用线程ID号作为迭代器。换句话说,你可以让CUDA内核总是运行10,000次,这意味着上面的代码适用于任何阵列,最多N = 10,000(当然对于较小的阵列,你会在if (tid < N)浪费周期。 )。

对于倾斜存储器(2D和3D阵列),CUDA编程指南有以下示例:

// Host code
int width = 64, height = 64; 
float* devPtr; size_t pitch; 
cudaMallocPitch(&devPtr, &pitch, width * sizeof(float), height);

MyKernel<<<100, 512>>>(devPtr, pitch, width, height); 

// Device code 
__global__ void MyKernel(float* devPtr, size_t pitch, int width, int height) 
{ 
    for (int r = 0; r < height; ++r) {
        float* row = (float*)((char*)devPtr + r * pitch); 
        for (int c = 0; c > width; ++c) { 
            float element = row[c]; 
        }
    }
}

这个例子对我来说似乎没什么用处。首先,他们声明一个64 x 64的数组,然后将内核设置为执行512 x 100次。没关系,因为内核除了遍历数组之外什么都不做(所以它通过64 x 64阵列运行51,200个循环)。

根据this answer,当有线程块进行时的迭代器将是

int tid = (blockIdx.x * blockDim.x) + threadIdx.x;

因此,如果我想在我的问题中运行第一个片段,我可以确保我有足够的块和线程来覆盖每个元素,包括我不关心的填充。但这似乎很浪费。

那么如何在不经过填充元素的情况下迭代一个投放的数组呢?

在我的特定应用程序中,我有一个2D FFT,我试图计算幅度和角度的数组(在GPU上以节省时间)。

1 个答案:

答案 0 :(得分:1)

在回顾了JackOLantern的宝贵意见和答案,并重新阅读文档后,我能够直截了当地说出来。当然答案是&#34;琐碎的&#34;现在我明白了。

在下面的代码中,我定义CFPtype(复杂浮点)和FPtype,以便我可以在单精度和双精度之间快速切换。例如,#define CFPtype cufftComplex

我仍然无法绕过用于调用内核的线程数。如果它太大,它根本就不会进入该功能。文档似乎没有说明应该使用什么号码 - 但这只是一个单独的问题。

让我的整个程序工作的关键(在内存上进行2D FFT并计算幅度和参数)意识到即使CUDA为你提供了大量的显而易见的&#34;帮助分配2D和3D数组,一切仍然是字节的单位。在malloc调用中显而易见的是必须包含sizeof(type),但在allocate(width, height)类型的调用中我完全错过了它。我猜,Noob错了。如果我写了库,我会把类型大小作为一个单独的参数,但无论如何。

因此,给定尺寸为width x height的图像(以像素为单位),这就是它的结合方式:

分配内存

我在主机端使用固定内存,因为它应该更快。用cudaHostAlloc分配的内容很简单。对于音调内存,您需要存储每个不同宽度和类型的音高,因为它可能会改变。在我的情况下,维度都是相同的(复杂到复杂的转换),但我有实数的数组,所以我存储了complexPitchrealPitch。倾斜的内存是这样完成的:

cudaMallocPitch(&inputGPU, &complexPitch, width * sizeof(CFPtype), height);

要将内存复制到音调数组中,您无法使用cudaMemcpy

cudaMemcpy2D(inputGPU, complexPitch,  //destination and destination pitch
inputPinned, width * sizeof(CFPtype), //source and source pitch (= width because it's not padded).
width * sizeof(CFPtype), height, cudaMemcpyKind::cudaMemcpyHostToDevice);

投放数组的FFT计划

JackOLantern provided this answer,我无法做到。就我而言,计划看起来像这样:

int n[] = {height, width};
int nembed[] = {height, complexPitch/sizeof(CFPtype)};
result = cufftPlanMany(
    &plan, 
    2, n, //transform rank and dimensions
    nembed, 1, //input array physical dimensions and stride
    1, //input distance to next batch (irrelevant because we are only doing 1)
    nembed, 1, //output array physical dimensions and stride
    1, //output distance to next batch
    cufftType::CUFFT_C2C, 1);

执行FFT非常简单:

cufftExecC2C(plan, inputGPU, outputGPU, CUFFT_FORWARD);

到目前为止,我几乎没有优化。现在我想要获得变换的幅度和相位,因此存在如何并行遍历音调阵列的问题。首先,我定义一个函数来调用内核,使用&#34;更正&#34;每个块的线程和足够的块来覆盖整个图像。正如文档所建议的,为这些数字创建2D结构是一个很大的帮助。

void GPUCalcMagPhase(CFPtype *data, size_t dataPitch, int width, int height, FPtype *magnitude, FPtype *phase, size_t magPhasePitch, int cudaBlockSize)
{
    dim3 threadsPerBlock(cudaBlockSize, cudaBlockSize);
    dim3 numBlocks((unsigned int)ceil(width / (double)threadsPerBlock.x), (unsigned int)ceil(height / (double)threadsPerBlock.y));

    CalcMagPhaseKernel<<<numBlocks, threadsPerBlock>>>(data, dataPitch, width, height, magnitude, phase, magPhasePitch);
}

设置每个块的块和线程相当于编写(最多3个)嵌套的for - 循环。所以你必须有足够的块*线程来覆盖数组,然后在内核中你必须确保你没有超过数组大小。通过对threadsPerBlocknumBlocks使用2D元素,您可以避免必须遍历数组中的填充元素。

并行遍历投放数组

内核使用文档中的标准指针算法:

__global__ void CalcMagPhaseKernel(CFPtype *data, size_t dataPitch, int width, int height,
                                   FPtype *magnitude, FPtype *phase, size_t magPhasePitch)
{
    int threadX = threadIdx.x + blockDim.x * blockIdx.x;
    if (threadX >= width) 
        return;

    int threadY = threadIdx.y + blockDim.y * blockIdx.y;
    if (threadY >= height)
        return;

    CFPtype *threadRow = (CFPtype *)((char *)data + threadY * dataPitch);
    CFPtype complex = threadRow[threadX];

    FPtype *magRow = (FPtype *)((char *)magnitude + threadY * magPhasePitch);
    FPtype *magElement = &(magRow[threadX]);

    FPtype *phaseRow = (FPtype *)((char *)phase + threadY * magPhasePitch);
    FPtype *phaseElement = &(phaseRow[threadX]);

    *magElement = sqrt(complex.x*complex.x + complex.y*complex.y);
    *phaseElement = atan2(complex.y, complex.x);
}

这里唯一浪费的线程是针对宽度或高度不是每个块的线程数的倍数的情况。