为什么在GPU编程中需要工作效率?


13

我一直在阅读以下有关如何在CUDA中进行并行扫描的文章:

https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch39.html

在这篇文章中,重点是使扫描“高效工作”。换句话说,GPU算法执行的加法运算应不超过CPU算法O(n)。作者提出了两种算法,一种是“天真的”算法,可以进行O(nlogn)加法运算,另一种则认为“工作效率高”。但是,高效工作的算法执行的循环迭代次数是原来的两倍。

据我了解,GPU只是巨型SIMD处理器,应该同步运行。在“高效工作”算法中执行两倍的循环似乎意味着从长远来看,许多线程将处于空闲状态并降低性能。我想念什么?

Answers:


21

首先,请再说一遍:“ GPU只是巨型SIMD处理器,应该按步操作”,这要复杂得多。在整个 GPU不步调一致运行。着色器线程被分为32组,称为“扭曲”(在NVIDIA上;在AMD上,它们是64组,称为“波前”,但概念相同)。在一次扭曲中,所有线程都以SIMD数组的形式同步运行。但是,不同的经线彼此之间并非处于同步状态。此外,某些扭曲可能正在主动运行,而其他扭曲可能会被挂起,就像CPU线程一样。Warp可以由于正在等待某种东西(例如内存事务返回或清除障碍)而暂停,或者因为没有

现在,回到您的问题。我可以看到该论文中的“高效工作”算法看起来比“朴素”算法更有效率的两种方式。

  1. 高效的版本需要一半的线程才能开始。在朴素算法中,每个数组元素只有一个线程;但是在高效的版本中,每个线程都在数组的两个相邻元素上运行,因此它们只需要数组元素一半的线程。更少的线程意味着更少的经线,因此大部分的经线可以主动运行。

  2. 尽管工作效率高的版本需要更多步骤,但活动线程数减少得更快,并且所有迭代中活动线程的总数要小得多,这一事实抵消了这一点。如果一个warp在迭代过程中没有活动线程,则该warp只会跳到下一个障碍并被挂起,从而允许其他warp运行。因此,较少的活动扭曲通常可以在执行时间上获得回报。(这隐含的是,需要以这样的方式设计GPU代码:将活动线程打包到尽可能少的线程束中-您不希望它们分散地散布,因为即使一个活动线程也会强制整个线程束保持活跃。)

    考虑朴素算法中活动线程的数量。请看图2中的一篇文章中,你可以看到,所有的线程是活动的,除了前2 ķķ次迭代。因此,对于N个线程,活动线程的数量大约为N − 2 k。例如,如果N = 1024,则每次迭代的活动线程数为:

    1023, 1022, 1020, 1016, 1008, 992, 960, 896, 768, 512
    

    如果将其转换为活动的翘曲数(除以32并四舍五入),则会得到:

    32, 32, 32, 32, 32, 31, 30, 28, 24, 16
    

    总数为289。另一方面,高效的算法从一半的线程开始,然后将每次迭代的活动线程数量减半,直到降至1,然后开始加倍,直到恢复至再次将数组大小减半:

     512, 256, 128, 64, 32, 16, 8, 4, 2, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512
    

    将其转换为活动变形:

    16, 8, 4, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4, 8, 16
    

    总和为71,仅为总数的四分之一。因此,您可以看到,在整个操作过程中,使用高效工作算法的活动经纱数量要少得多。(实际上,对于中间的长时间运行,只有很少的活动扭曲,这意味着大部分芯片都没有被占用。如果还有其他计算任务在运行,例如来自其他CUDA流,它们可以扩展以填充空置的空间。)

话虽这么说,不幸的是,GPU Gems文章没有清楚地解释其中的任何一个问题,而是专注于big-O“增加数量”分析,尽管这种分析并非完全无关紧要,但却错过了许多关于此算法为何的细节。快点。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.