使用 Cuda 进行 128 位向量加法,性能问题

Posted

技术标签:

【中文标题】使用 Cuda 进行 128 位向量加法,性能问题【英文标题】:128-bit vector addition with Cuda, performance issue 【发布时间】:2020-08-25 03:42:47 【问题描述】:

我想添加带有进位的 128 位向量。我的 128 位版本(下面代码中的addKernel128)比基本的 32 位版本(下面的addKernel32)慢两倍。 我有内存合并问题吗?如何获得更好的性能?

#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <iostream>

#define UADDO(c, a, b) asm volatile("add.cc.u32 %0, %1, %2;" : "=r"(c) : "r"(a) , "r"(b));
#define UADDC(c, a, b) asm volatile("addc.cc.u32 %0, %1, %2;" : "=r"(c) : "r"(a) , "r"(b));

__global__ void addKernel32(unsigned int *c, const unsigned int *a, const unsigned int *b, const int size)

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

  while (tid < size)
  
    c[tid] = a[tid] + b[tid];
    tid += blockDim.x * gridDim.x;
  


__global__ void addKernel128(unsigned *c, const unsigned *a, const unsigned *b, const int size)

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

  while (tid < size / 4)
  
    uint4 a4 = ((const uint4 *)a)[tid],
          b4 = ((const uint4 *)b)[tid],
          c4;

    UADDO(c4.x, a4.x, b4.x)
    UADDC(c4.y, a4.y, b4.y) // add with carry
    UADDC(c4.z, a4.z, b4.z) // add with carry
    UADDC(c4.w, a4.w, b4.w) // add with carry (no overflow checking for clarity)

    ((uint4 *)c)[tid] = c4;

    tid += blockDim.x * gridDim.x;
  


int main()

  const int size = 10000000; // 10 million

  unsigned int *d_a, *d_b, *d_c;

  cudaMalloc((void**)&d_a, size * sizeof(int));
  cudaMalloc((void**)&d_b, size * sizeof(int));
  cudaMalloc((void**)&d_c, size * sizeof(int));

  cudaMemset(d_a, 1, size * sizeof(int)); // dummy init just for the example
  cudaMemset(d_b, 2, size * sizeof(int)); // dummy init just for the example
  cudaMemset(d_c, 0, size * sizeof(int));

  int nbThreads = 512;
  int nbBlocks = 1024; // for example

  cudaEvent_t start, stop;
  cudaEventCreate(&start);
  cudaEventCreate(&stop);
  cudaEventRecord(start);

  addKernel128<<<nbBlocks, nbThreads>>>(d_c, d_a, d_b, size);

  cudaEventRecord(stop);
  cudaEventSynchronize(stop);
  float m = 0;
  cudaEventElapsedTime(&m, start, stop);

  cudaFree(d_c);
  cudaFree(d_b);
  cudaFree(d_a);
  cudaDeviceReset();
  printf("Elapsed = %g\n", m);
  return 0;

【问题讨论】:

128 位添加需要更多的工作,不是吗?你不希望它比 32 位版本慢吗? 在全球范围内,工作量是相同的:确实,每次添加都需要多 4 倍的工作,但必要添加的数量减少了 4 倍。 你没有提到你在哪个平台上(windows 或 linux,哪个 GPU,CUDA 版本,是 WDDM 等),这对于时序分析非常重要。对于计时,在计时测量之前进行“热身”也很常见。当我运行代码的修改版本here(cuda 5.0、rhel 5.5、Quadro5000 GPU)时,我得到的结果是经过的时间大致相同(实际上 128 版本要快一些)。我猜你是在 Windows 上,如果使用 WDDM GPU,准确计时可能会非常困难。 如果你在windows下,我猜,也请确定你是在构建项目的调试版本还是发布版本。 怀疑,我在带有 WDDM 驱动程序的 Windows 7 下。 VS2010、Cuda 5.5、驱动程序版本 320.57 和 Quadro 600 GPU。在调试版本中观察到性能不佳。发布版本要好得多,但仍然慢了 20%。也许我们可以得出结论,在这种环境下测量时间是不可预测的。感谢您测试我的代码。 【参考方案1】:

由于各种原因,在 WDDM GPU 上对 CUDA 代码进行计时可能非常困难。其中大部分都围绕着这样一个事实,即 Windows 将 GPU 作为显示设备进行管理,这可能会在时序中引入各种伪影。一个例子是 windows 驱动程序和 WDDM 将为 GPU 批量工作,并可能在 CUDA GPU 工作的中间交错显示工作。

如果可能,请在 linux 或 windows GPU 上为您的 cuda 代码计时 在 TCC 模式下。 为了提高性能,总是在没有-G 开关的情况下构建。在 Visual Studio 中,这通常对应于构建版本,而不是项目的调试版本。 为了获得良好的性能比较,通常建议在实际测量计时结果之前进行一些“热身运行”。这些将消除“启动”和其他一次性测量问题,您更有可能获得明智的结果。您可能还希望多次运行代码并对结果取平均值。 通常还建议使用与您的 GPU 对应的 arch 标志进行编译,例如 -arch=sm_20 用于 cc2.0 GPU。

【讨论】:

并且通过使用 sm_20 标志进行编译,128 位版本比 32 位版本快一点。谢谢! 好点,更新答案作为附加建议

以上是关于使用 Cuda 进行 128 位向量加法,性能问题的主要内容,如果未能解决你的问题,请参考以下文章

cuda 编 程cuda 实现向量加法

cuda上的128位整数?

CUDA 加法与移位指令性能

使用 AVX2 对 2 个短整型向量进行向量加法

使用 CUDA 进行大整数加法

实验找出块大小对 cuda 程序速度的影响