本节书摘来自华章社区《CUDA C编程权威指南》一书中的第3章,第3.3节并行性的表现述,作者[美] 马克斯·格罗斯曼(Max Grossman) ,更多章节内容可以访问云栖社区“华章社区”公众号查看
3.3 并行性的表现
为更好地理解线程束执行的本质,将使用不同的执行配置分析下述的sumMatrixOn-GPU2D核函数。使用nvprof配置指标,可以有助于理解为什么有些网格/块的维数组合比其他的组合更好。这些练习会提供网格和块的启发式算法,这是CUDA编程人员必备的技能。
二维矩阵求和的核函数如下所示:
在接下来的部分,将使用生成的sumMatrix对块和网格配置执行试验。
3.3.1 用nvprof检测活跃的线程束
首先,需要生成一个参考结果作为性能基准。为此,要先测试一组基础线程块的配置,尤其是大小为(32,32),(32,16),(16,32)和(16,16)的线程块。前面介绍过,sumMatrix接收线程块配置的x维作为它的第一个参数,接收线程块配置的y维作为它的第二个参数。通过用适当的命令行参数调用sumMatrix测试各种线程块配置。
在Tesla M2070上输出以下结果:
从结果中可以观察到两件事:
因为第二种情况中的块数比第一种情况的多,所以设备就可以有更多活跃的线程束。其原因可能是第二种情况与第一种情况相比有更高的可实现占用率和更好的性能。
第四种情况有最高的可实现占用率,但它不是最快的,因此,更高的占用率并不一定意味着有更高的性能。肯定有其他因素限制GPU的性能。
3.3.2 用nvprof检测内存操作
在sumMatrix内核(C[idx]=A[idx]+B[idx])中有3个内存操作:两个内存加载和一个内存存储。可以使用nvprof检测这些内存操作的效率。首先,用gld_throughput指标检查内核的内存读取效率,从而得到每个执行配置的差异:
从上述结果可知,最后两种情况下的加载效率是最前面两种情况的一半。这可以解释为什么最后两种情况下更高的加载吞吐量和可实现占用率没有产生较好的性能。尽管在最后两种情况下正在执行的加载数量(即吞吐量)很多,但是那些加载的有效性(即效率)是较低的。
注意,最后两种情况的共同特征是它们在最内层维数中块的大小是线程束的一半。如前所述,对网格和块启发式算法来说,最内层的维数应该总是线程束大小的倍数。第4章将讨论半个线程束大小的线程块是如何影响性能的。
3.3.3 增大并行性
从前一节可以总结出,一个块的最内层维数(block.x)应该是线程束大小的倍数。这样能极大地提高了加载效率。你可能对以下问题仍然很好奇:
调整block.x会进一步增加加载吞吐量吗
有其他方法可以增大并行性吗
现在已经建立了一个性能基准,可以通过测试sumMatrix使用更大范围的线程配置来回答这些问题:
从这些结果中可以总结出以下规律:
最后一次的执行配置块的大小为(256,8),这是无效的。一个块中线程总数超过了1 024个(这是GPU的硬件限制)。
最好的结果是第四种情况,块大小为(128,2)。
第一种情况中块大小为(64,2),尽管在这种情况下启动的线程块最多,但不是最快的配置。
因为第二种情况中块的配置为(64,4),与最好的情况有相同数量的线程块,这两种情况应该在设备上显示出相同的并行性。因为这种情况相比(128,2)仍然表现较差,所以你可以得出这样的结论:线程块最内层维度的大小对性能起着的关键的作用。这正重复了前一节中总结的结论。
在所有其他情况下,线程块的数量都比最好的情况少。因此,增大并行性仍然是性能优化的一个重要因素。
你可能会想,线程块最少的那些示例应该显示出较低的可实现占用率,线程块最多的那些例子应该显示出较高的可实现占用率。这个理论可以用nvprof检测achieved_occu-pancy指标来验证一下:
值得注意的是,最好的执行配置既不具有最高的可实现占用率,也不具有最高的加载吞吐量。从这些实验中可以推断出,没有一个单独的指标能直接优化性能。我们需要在几个相关的指标间寻找一个恰当的平衡来达到最佳的总体性能。
指标与性能
在大部分情况下,一个单独的指标不能产生最佳的性能
与总体性能最直接相关的指标或事件取决于内核代码的本质
在相关的指标与事件之间寻求一个好的平衡
从不同角度查看内核以寻找相关指标间的平衡
网格/块启发式算法为性能调节提供了一个很好的起点