1.6 控制并行资源
accParaCounter.cpp中的嵌套循环结构可以用来控制循环并行性,因此可以控制并行资源消耗。
大多数并行编程人员遵循在系统中利用最大并行性来实现最高性能的方法。
大量线程背后的思路是给予并行调度器尽可能多的线程以供调度,从而最大化地利用所有计算资源。GPU编程人员喜欢用占有率(occupancy)作为线程并行度的度量。高占有率意味着调度器有更多的激活线程以供调用,因此有机会实现更高性能。
高占有率并不一定转为最快的应用性能。指令级并行(Instruction level parallelism,ILP)通过较少的线程来保持处理器(或GPU)忙碌以隐藏线程等待时间,线程数越少,消耗的资源和开销越少。指令级并行同样可以获得高性能。但是,编程人员必须安排好计算以确保最好地使用并行硬件。使用ILP的原因简单却有效:使用较少的线程意味着每个线程可以使用更多的资源(Volkov, 2010)。
这个结论同样适用于OpenACC,用来控制并行资源的使用。不同于OpenMP,OpenACC不提供类似omp_get_thread_num()机制来确定线程的身份,也不提供类似omp_get_num_thread()机制来找出并发线程总数。因此,无法写出可以控制其并行资源使用的类。相反,至少OpenACC 2.5规范中,OpenACC开发人员必须使用嵌套循环结构来显式控制在其程序中如何使用并行资源。
例如,程序accParaRNG.cpp并行地利用多个顺序随机数生成器。为了成功执行,程序必须保证同时只有一个线程使用随机数生成器。否则的话,内部更新的随机数生成器状态信息可能会损坏。不能用单个原子操作来更新种子区域,在OpenACC中唯一确保正确的操作是使用一个嵌套循环集。
如图1-23和1-24所示,accParaNRG.cpp代码使用任务并行和本章讨论的OpenACC构建,以及带进位乘法(multiply-with-carry, MWC)随机数生成器。George Marsaglia发明了MWC随机数生成器,基于从两个到数千个随机选择的种子值的初始集合来生成随机整数序列(Marsaglia & Zaman,1991)。MWC方法的主要优势是可以用少量C++代码实现,并且只有整数运算。MWC仍然主导快速生成随机数序列。读者可以用其他更精细的随机数生成器来替换accParaNRG.cpp中的算法,例如广泛使用的梅森绞扭器(Matsumoto & Nishinura, 1998)和Dieharder随机数测试套件(Brown, 2016)。
组合图1-23和图1-24中的代码,放到accParaRNG.cpp中。编译accParaRNG.cpp成PGI统一的二进制,在CPU和GPU上的测试结果如图1-25所示: