CUDA:2D数组索引给出了意想不到的结果

时间:2013-06-03 21:10:21

标签: c++ cuda

我开始学习CUDA,我想编写一个简单的程序,将一些数据复制到GPU,修改它并将其传回。我已经在谷歌上搜索并试图找出我的错误。我很确定问题出现在我的内核中,但我不确定是什么问题。

这是我的内核:

__global__ void doStuff(float* data, float* result)
{
    if (threadIdx.x < 9) // take the first 9 threads
    {
        int index = threadIdx.x;
        result[index] = (float) index;
    }
}

以下是我main的相关部分:

#include <stdlib.h>
#include <stdio.h>

int main(void)
{
    /*
        Setup
    */
    float simple[] = {-1.0, -2.0, -3.0, -4.0, -5.0, -6.0, -7.0, -8.0, -9.0};

    float* data_array;
    float* result_array;

    size_t data_array_pitch, result_array_pitch;
    int width_in_bytes = 3 * sizeof(float);
    int height = 3;

    /*
        Initialize GPU arrays
    */
    cudaMallocPitch(&data_array, &data_array_pitch, width_in_bytes, height);
    cudaMallocPitch(&result_array, &result_array_pitch, width_in_bytes, height);

    /*
        Copy data to GPU
    */
    cudaMemcpy2D(data_array, data_array_pitch, simple, width_in_bytes, width_in_bytes, height, cudaMemcpyHostToDevice);

    dim3 threads_per_block(16, 16);
    dim3 num_blocks(1,1);

    /*
        Do stuff
    */
    doStuff<<<num_blocks, threads_per_blocks>>>(data_array, result_array);

    /*
        Get the results
    */
    cudaMemcpy2D(simple, width_in_bytes, result_array, result_array_pitch, width_in_bytes, height, cudaMemcpyDeviceToHost);

    for (int i = 1; i <= 9; ++i)
    {
        printf("%f ", simple[i-1]);
        if(!(i%3))
            printf("\n");
    }

    return 0;
}

当我运行这个时,第一行得到0.000000 1.000000 2.00000而另外两行得到垃圾。

2 个答案:

答案 0 :(得分:2)

如果你刚刚开始学习cuda,我不确定我会专注于2D阵列。

如果您在代码中手动输入代码,也很奇怪,因为您定义了threads_per_block变量,但在内核调用中使用了threads_per_blocks

无论如何,您的代码存在一些问题:

  1. 使用2D数组时,几乎总是需要传递音高 参数(以某种方式)到内核。 cudaMallocPitch 在每行的末尾分配带有额外填充的数组,以便这样做 下一行从一个很好的对齐边界开始。这通常会 导致分配粒度为128或256字节。所以你的第一个 row有3个有效数据实体,后跟足够的空白空间来填充 比如说256个字节(等于你的音高变量)。所以我们必须改变内核调用和内核本身来解决这个问题。
  2. 您的内核本质上是一维内核(例如,它不理解或使用threadIdx.y)。因此,启动2D网格没有意义。虽然在这种情况下它不会造成任何伤害,但它会产生冗余,这在其他代码中可能会令人困惑和麻烦。
  3. 根据以上评论,这是一个更新的代码,显示了一些可以为您提供预期结果的更改:

    #include <stdio.h>
    
    
    __global__ void doStuff(float* data, float* result, size_t dpitch, size_t rpitch, int width)
    {
        if (threadIdx.x < 9) // take the first 9 threads
        {
            int index = threadIdx.x;
            result[((index/width)*(rpitch/sizeof(float)))+ (index%width)] = (float) index;
        }
    }
    
    int main(void)
    {
        /*
            Setup
        */
        float simple[] = {-1.0, -2.0, -3.0, -4.0, -5.0, -6.0, -7.0, -8.0, -9.0};
    
        float* data_array;
        float* result_array;
    
        size_t data_array_pitch, result_array_pitch;
        int height = 3;
        int width = 3;
        int width_in_bytes = width * sizeof(float);
    
        /*
            Initialize GPU arrays
        */
        cudaMallocPitch(&data_array, &data_array_pitch, width_in_bytes, height);
        cudaMallocPitch(&result_array, &result_array_pitch, width_in_bytes, height);
    
        /*
            Copy data to GPU
        */
        cudaMemcpy2D(data_array, data_array_pitch, simple, width_in_bytes, width_in_bytes, height, cudaMemcpyHostToDevice);
    
        dim3 threads_per_block(16);
        dim3 num_blocks(1,1);
    
        /*
            Do stuff
        */
        doStuff<<<num_blocks, threads_per_block>>>(data_array, result_array, data_array_pitch, result_array_pitch, width);
    
        /*
            Get the results
        */
        cudaMemcpy2D(simple, width_in_bytes, result_array, result_array_pitch, width_in_bytes, height, cudaMemcpyDeviceToHost);
    
        for (int i = 1; i <= 9; ++i)
        {
            printf("%f ", simple[i-1]);
            if(!(i%3))
                printf("\n");
        }
        return 0;
    }
    

    您可能还会发现this question有趣的阅读材料。

    编辑:回复评论中的问题:

    result[((index/width)*(rpitch/sizeof(float)))+ (index%width)] = (float) index;
                  1               2                      3
    

    要计算投放数组中正确的元素索引,我们必须:

    1. 计算线程索引中的(虚拟)行索引。我们通过将线程索引的整数除法除以每个(非音调)行的宽度(在元素中,而不是字节中)来实现此目的。
    2. 将行索引乘以每个 pitched 行的宽度。每个 pitched 行的宽度由pitched参数给出,该参数以字节为单位。要将此音调字节参数转换为音调元素参数,我们除以每个元素的大小。然后,通过将数量乘以在步骤1中计算的行索引,我们现在已经索引到正确的行。
    3. 通过将线程索引的余数(模除)除以宽度(在元素中),从线程索引计算(虚拟)列索引。一旦我们得到列索引(在元素中),我们将它添加到在步骤2中计算的正确行开始索引,以标识该线程将负责的元素。
    4. 以上是相对简单的操作的相当大的努力,这是为什么我建议首先关注基本的cuda概念而不是倾斜阵列的一个例子。例如,我将在处理倾斜阵列之前计算如何处理1和2D线程块以及1和2D网格。在某些情况下,倾斜数组是一种有用的性能增强器,用于访问2D数组(或3D数组),但在CUDA中处理多维数组并不是必需的。

答案 1 :(得分:0)

实际上也可以通过替换

来完成
int width_in_bytes = 3 * sizeof(float);

由:

int width_in_bytes = sizeof(float)*9;

因为这是告诉cudaMemcpy2D要从src复制到dst的字节数的参数,在第一个代码中要求复制3个浮点数,但是要复制的数组长度为9,所以你需要的宽度是9个浮点数的大小。

虽然此解决方案有效,但代码仍然存在一些效率低下的问题;例如,如果你真的希望块的前9个线程做某事,在'if'中你应该用和(&amp;&amp;)

添加以下条件
threadIdx.y==0