【问题标题】:why CUDA doesn't result in speedup in C++ code?为什么 CUDA 不会导致 C++ 代码加速?
【发布时间】:2020-05-21 22:48:40
【问题描述】:

我正在使用 VS2019 并拥有 NVIDIA GeForce GPU。我尝试了此链接中的代码:https://towardsdatascience.com/writing-lightning-fast-code-with-cuda-c18677dcdd5f

该帖子的作者声称在使用 CUDA 时获得了加速。但是,对我来说,串行版本大约需要 7 毫秒,而 CUDA 版本大约需要 28 毫秒。为什么这段代码的 CUDA 速度较慢?我使用的代码如下:

__global__
void add(int n, float* x, float* y)
{

    int index = blockIdx.x * blockDim.x + threadIdx.x;
    int stride = blockDim.x * gridDim.x;
    for (int i = index; i < n; i += stride)
        y[i] = x[i] + y[i];
}

void addSerial(int n, float* x, float* y)
{
    for (int i = 0; i < n; i++)
        y[i] = x[i] + y[i];
}

int main()
{
    int NSerial = 1 << 20;   
    float* xSerial = new float[NSerial];
    float* ySerial = new float[NSerial];
    for (int i = 0; i < NSerial; i++) {
        xSerial[i] = 1.0f;
        ySerial[i] = 2.0f;
    }
    auto t1Serial = std::chrono::high_resolution_clock::now();
    addSerial(NSerial, xSerial, ySerial);
    auto t2Serial = std::chrono::high_resolution_clock::now(); 
    auto durationSerial = std::chrono::duration_cast<std::chrono::milliseconds>(t2Serial - t1Serial).count(); 
    float maxErrorSerial = 0.0f;
    for (int i = 0; i < NSerial; i++)
        maxErrorSerial = fmax(maxErrorSerial, fabs(ySerial[i] - 3.0f));
    std::cout << "Max error Serial: " << maxErrorSerial << std::endl;
    std::cout << "durationSerial: "<<durationSerial << std::endl;
    delete[] xSerial;
    delete[] ySerial;


    int N = 1 << 20;   

    float* x, * y;
    cudaMallocManaged(&x, N * sizeof(float));
    cudaMallocManaged(&y, N * sizeof(float));

    for (int i = 0; i < N; i++) {
        x[i] = 1.0f;
        y[i] = 2.0f;
    }


    int device = -1;
    cudaGetDevice(&device);
    cudaMemPrefetchAsync(x, N * sizeof(float), device, NULL);
    cudaMemPrefetchAsync(y, N * sizeof(float), device, NULL);


    int blockSize = 1024;
    int numBlocks = (N + blockSize - 1) / blockSize;
    auto t1 = std::chrono::high_resolution_clock::now();
    add << <numBlocks, blockSize >> > (N, x, y);

    cudaDeviceSynchronize();
    auto t2 = std::chrono::high_resolution_clock::now(); 
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count(); 

    float maxError = 0.0f;
    for (int i = 0; i < N; i++)
        maxError = fmax(maxError, fabs(y[i] - 3.0f));
    std::cout << "Max error: " << maxError << std::endl;
    std::cout << "duration CUDA: "<<duration; 

    cudaFree(x);
    cudaFree(y);



    return 0;
}

【问题讨论】:

  • 7 毫秒可能太少了,也看不到任何效果。尝试增加工作量
  • 当我更改NSerial = N = 1&lt;&lt;30时,串行版本只需要4秒而CUDA需要19秒
  • 这可能是因为将内存从 RAM 移动到 GPU 比在 CPU 上进行简单计算需要更长的时间。这取决于硬件。
  • 循环运行内核约 100 次并取平均值。您的同步调用也会影响性能。
  • 对此类问题的规范答案似乎是“尝试运行以发布模式构建的代码,而不是调试”。

标签: c++ parallel-processing cuda gpu


【解决方案1】:

这里有几个观察:

  1. CUDA 内核的第一次调用可能会累积大量与 GPU 上的设置相关的单次延迟,因此通常的方法是包含“热身”调用
  2. 您问题中的内核设计是“驻留”设计,因此当您仅启动所需数量的块以完全占用您的 GPU 时,应该会实现最佳执行。您可以使用 API 为您的 GPU 获取此信息。
  3. 以微秒而不是毫秒为单位执行计时
  4. 在发布模式下构建您的代码。

对您的 CUDA 代码执行所有这些操作让我得到这个:

    int N = 1 << 20;   
    int device = -1;
    cudaGetDevice(&device);

    float* x, * y;
    cudaMallocManaged(&x, N * sizeof(float));
    cudaMallocManaged(&y, N * sizeof(float));

    for (int i = 0; i < N; i++) {
        x[i] = 1.0f;
        y[i] = 2.0f;
    }
    cudaMemPrefetchAsync(x, N * sizeof(float), device, NULL);
    cudaMemPrefetchAsync(y, N * sizeof(float), device, NULL);

    int blockSize, numBlocks;
    cudaOccupancyMaxPotentialBlockSize(&numBlocks, &blockSize, add);

    for(int rep=0; rep<10; rep++) {
        auto t1 = std::chrono::high_resolution_clock::now();
        add << <numBlocks, blockSize >> > (N, x, y);
        cudaDeviceSynchronize();
        auto t2 = std::chrono::high_resolution_clock::now(); 
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1).count(); 
        std::cout << rep << " duration CUDA: " << duration <<std::endl; 
    }

    float maxError = 0.0f;
    for (int i = 0; i < N; i++)
        maxError = fmax(maxError, fabs(y[i] - 12.0f));
    std::cout << "Max error: " << maxError << std::endl;

    cudaFree(x);
    cudaFree(y);

构建并运行它:

$ nvcc -arch=sm_52 -std=c++11 -o not_so_fast not_so_fast.cu 
$ ./not_so_fast 
Max error Serial: 0
durationSerial: 2762
0 duration CUDA: 1074
1 duration CUDA: 150
2 duration CUDA: 151
3 duration CUDA: 158
4 duration CUDA: 152
5 duration CUDA: 152
6 duration CUDA: 147
7 duration CUDA: 124
8 duration CUDA: 112
9 duration CUDA: 113
Max error: 0

在我的系统上,第一个 GPU 的运行速度几乎是串行循环的三倍。第二次和随后的运行再次快了近 10 倍。您的结果可能(并且可能会)有所不同。

【讨论】:

  • 当我在 VS2019 中尝试时,串行需要 780us,第二次和随后的 CUDA 运行需要大约 350us,所以只有大约 2 倍的加速
  • 就像我说的,您的结果可能会有所不同
猜你喜欢
  • 2023-04-02
  • 1970-01-01
  • 1970-01-01
  • 2020-08-20
  • 1970-01-01
  • 2013-01-18
  • 1970-01-01
  • 1970-01-01
  • 2016-10-25
相关资源
最近更新 更多