我有一个应用程序,可以在用户系统中的GPU之间分配处理负载。基本上,每个GPU有一个CPU线程,当由主应用程序线程周期性地触发时,它启动一个GPU处理间隔。使用2个GPU同时调用cudaMalloc时的性能不佳
考虑以下图像(使用NVIDIA的CUDA分析器工具生成),以GPU处理间隔为例 - 此处应用程序使用单个GPU。
正如你所看到的GPU处理时间的很大一部分是由两个分拣作业消耗,我使用这个(推力:: sort_by_key)的推力库。另外,在启动实际排序之前,它看起来像thrust :: sort_by_key在引擎盖下调用一些cudaMallocs。
现在考虑同样的处理间隔,其中应用已经普及的处理负荷超过两个GPU:
在一个完美的世界里,你所期望的2 GPU处理间隔正好一半的单GPU(因为每个GPU都在做一半的工作)。正如您所看到的,这并非部分原因,因为由于某种争用问题,cudaMallocs在同时调用时(有时延长2-3倍)似乎需要更长的时间。我不明白为什么需要这样做,因为这两个GPU的内存分配空间是完全独立的,所以不应该有cudaMalloc上的全系统锁定 - 每个GPU锁定会更合理。
为了证明我的假设,即同时发生的cudaMalloc调用问题,我创建了一个带有两个CPU线程(每个GPU)的可笑简单程序,每个线程多次调用cudaMalloc。我第一次运行这个程序,使得单独的线程不会在同一时间拨打cudaMalloc:
你看,它需要每〜175分配微秒。接下来,我跑的程序与线程同时调用cudaMalloc:
在这里,每个呼叫拿了〜538微秒或比以前的情况下,长3倍!毋庸置疑,这大大减缓了我的应用程序的运行速度,而且这意味着只有2个以上的GPU才会导致问题恶化。
我已经注意到在Linux和Windows上的这种行为。在Linux上,我使用的是Nvidia驱动程序版本319.60,在Windows上我使用的是327.23版本。我正在使用CUDA工具包5.5。
可能的原因: 我在这些测试中使用GTX 690。这张卡基本上是2 680个像GPU一样的单元。这是我运行过的唯一的“多GPU”设置,所以或许cudaMalloc问题与690的2个GPU之间的一些硬件依赖有关?
高性能代码的常见建议是使malloc操作脱离任何性能循环。我意识到这并不是一个微不足道的问题,因为你使用推力。有高性能的排序库可以替代推力sort_by_key,这将允许您提前进行分配并将其重新用于排序操作。 [CUB](http://nvlabs.github.io/cub/),[b40c](http://code.google.com/p/back40computing/)和[MGPU](http://nvlabs.github .io/moderngpu /)都是可能的。 –
是的,我已经看过CUB和B40C(B40C网站说该项目已被弃用)。在我做清除推力的工作之前,我想看看图书馆之间的一些比较图。你能指点我一些表演数字吗?你推荐哪个图书馆? ......似乎推力并不是非常高的性能,例如,我已经用我自己的定制内核交换了一堆推力:: reduce和reduce_by_key调用 - 这样做将我的处理时间减半。不是开玩笑。 – rmccabe3701
推力实际上是基于b40c的一个特定变体(或曾经是)。对于等效的测试用例,我在b40c和MGPU之间的测试没有太大差异。在我运行的一个测试中,我只对32位值的22位进行排序。 MGPU有一个拨号盘,我可以转而只用22位分辨率,我观察到这样做的速度提高了40%。我没有使用CUB多。如果你浏览这些链接,你可能会发现一些性能数据。例如,某些MGPU性能数据[here](http://nvlabs.github.io/moderngpu/performance.html#performance) –