GPGPU:块大小对程序性能的影响,为什么我的程序在非常特定的大小下运行得更快?

时间:2016-12-31 02:55:13

标签: performance cuda performance-testing gpgpu

我的Cuda程序可以显着提升性能(平均而言),具体取决于块的大小和数量。块数;其中"线程的总数"保持原样。 (我不确定线程​​是否是正确的术语......但是我将在这里使用它;每个内核的总线程数是(块数)*(块大小)) 。我制作了一些图表来说明我的观点。

但首先请允许我解释一下我的算法是什么,然而我不确定它的相关性,因为我认为这适用于所有GPGPU程序。但是也许我错了。

基本上我会遇到逻辑上被视为2D数组的大型数组,其中每个线程从数组中添加一个元素,并将该值的平方添加到另一个变量,然后在最后将值写入另一个数组,在每次读取期间,所有线程都以某种方式移位。这是我的内核代码:

__global__ void MoveoutAndStackCuda(const float* __restrict__ prestackTraces, float* __restrict__ stackTracesOut,
  float* __restrict__ powerTracesOut, const int* __restrict__ sampleShift,
  const unsigned int samplesPerT, const unsigned int readIns,
  const unsigned int readWidth, const unsigned int defaultOffset) {

  unsigned int globalId = ((blockIdx.x * blockDim.x) + threadIdx.x); // Global ID of this thread, starting from 0 to total # of threads

  unsigned int jobNum = (globalId / readWidth); // Which array within the overall program this thread works on
  unsigned int readIndex = (globalId % readWidth) + defaultOffset; // Which sample within the array this thread works on

  globalId = (jobNum * samplesPerT) + readIndex;  // Incorperate default offset (since default offset will also be the offset of
                                                  // index we will be writing to), actual globalID only needed for above two variables.

  float stackF = 0.0;
  float powerF = 0.0;

  for (unsigned int x = 0; x < readIns; x++) {

    unsigned int indexRead = x + (jobNum * readIns);

    float value = prestackTraces[readIndex + (x * samplesPerT) + sampleShift[indexRead]];

    stackF += value;
    powerF += (value * value);
  }

  stackTracesOut[globalId] = stackF;
  powerTracesOut[globalId] = powerF;
}

现在为了这篇文章的内容,在调用此代码时

  MoveoutAndStackCuda<<<threadGroups, threadsPerGroup>>>(*prestackTracesCudaPtr,
    *stackTracesOutCudaPtr, *powerTracesOutCudaPtr,
    *sampleShiftCudaPtr, samplesPerT, readIns,
    readWidth, defaultOffset);

我所做的只是&lt;&lt;&lt;&gt;&gt;&gt;内的threadGroups和threadsPerGroup不同,其中threadGroups.x * threadsPerGroup.x保持不变。 (如前所述,这是一维问题)。

我将块大小增加64,直到达到1024.我预计没有变化,因为我认为只要块大小大于32,我相信它是核心中的ALU数量,它会运行得那么快尽可能。看看我制作的这张图:

Cuda performance as block size increases

对于此特定大小,线程总数为5000 * 5120,因此,例如,如果块大小为64,则存在((5000 * 5120)/ 64)个块。 出于某种原因,块大小为896,768和512时性能会有显着提升。为什么?

我知道这看起来是随机的,但是这个图中的每个点都是50个测试平均值!

这是另一个图表,这次是线程总数为(8000 * 8192)的时间。这次的提升是768和960。

Cuda performance as block size increases

又一个例子,这次是一个小于其他两个问题的工作(总线程数为2000 * 2048):

Cuda performance as block size increases

事实上,这是我用这些图表制作的相册,每张图表代表问题的不同大小:graph album

我正在运行这个Quadro M5000,它有2048个Cuda核心。我相信每个Cuda Core都有32个ALU,所以我认为在任何给定时间可能发生的计算总数是(2048 * 32)?

那么是什么解释了这些神奇的数字?我认为它可能是线程的总数除以cuda核心的数量,或者除以(2048 * 32),但到目前为止,我发现没有与我的相册中所有图形的任何内容相关联。是否还有其他测试可以帮助缩小范围?我想找出运行此程序的块大小以获得最佳结果。

此外,我没有包含它,但我也做了一个测试,其中块大小从32减少了1并且事情变得指数级慢。这对我来说很有意义,因为我们每组的局部线程数比给定多处理器中的ALU少。

1 个答案:

答案 0 :(得分:5)

基于此声明:

  

我将块大小增加64,直到达到1024.我预计没有变化,因为我认为只要块大小大于32,我相信它是核心中的ALU数量,它会运行得那么快尽可能。

我想说有一个关于GPU的重要概念你可能不知道:GPU是一个“延迟隐藏”机器。它们主要通过暴露大量可用(并行)工作来隐藏延迟。这可以粗略地概括为“许多线程”。使用GPU时,如果有足够的线程来覆盖“核心”或执行单元的数量,这是一个完全错误的想法,这就足够了。 不是。

作为(初学者)GPU程序员,您应该忽略GPU中的核心数量。你想要很多的线程。在内核级别和每个GPU SM。

通常,当您为每个SM提供更多线程时,GPU在执行其他有用工作时隐藏延迟的能力会增加。这解释了所有图表的一般趋势,即斜率通常从左向右向下(即平均性能增加,通常,因为您为每个SM提供了更多的暴露工作)。

然而,这并未涉及高峰和低谷。 GPU具有大量可能影响性能的架构问题。我不会在这里提供完整的治疗方法。但是我们来看一个案例:

  

为什么第一个图表中的性能增加到512个线程,然后突然减少到576个线程?

这很可能是占用效果。 GPU中的SM最多可以补充2048个线程。基于前面的讨论,当我们最大化线程补码时,SM将具有隐藏延迟(因此通常提供最大平均性能)的最大能力,最高可达2048。

对于512个线程的块大小,我们可以在SM上准确地匹配这些线程块中的4个,然后它将具有2048个线程的补充,可以从中选择工作和延迟隐藏。

但是当您将线程块大小更改为576时,4 * 576&gt; 2048,所以我们不能再在每个SM上放置4个线程块。这意味着,对于该内核配置,每个SM将以3个线程块运行,即2048个中的1728个线程可能。从SM的角度来看,这实际上是更糟,而不是之前允许2048个线程的情况,因此它可能是性能从512减少到576个线程的指标(就像它增加了一样)从448到512,其中涉及瞬时占用的类似变化。)

由于上述原因,当我们改变每个块的线程时,看到你所展示的性能图表并不罕见。

具有粒度(量化)效果的其他占用限制器可能会在性能图中产生类似的峰值行为。例如,你的问题中没有足够的信息来推测每线程寄存器的使用情况,但占用限制器可能是每个线程使用的寄存器。当您更改线程补码时,您会发现每个SM可能会有类似的块替换,这可能会导致占用率(上下)变化,从而改变性能。

为了进一步深入研究,我建议您花些时间了解各种分析器的占用率,每个线程的寄存器和性能分析功能。有很多关于这些主题的信息;谷歌是你的朋友,并注意上面评论中链接的question/answers作为一个合理的起点。要充分研究占用率及其对性能的影响,需要比您在此处提供的信息更多的信息。它基本上需要MCVE以及确切的编译命令行,以及您运行的平台和CUDA版本。编译器的每线程寄存器使用情况受到所有这些因素的影响,其中大部分都没有提供。