观点来源:ARM公司的Beau Paisley和CURTISS-WRIGHTDEFENSE SOLUTIONS公司的Tammy Carter
目前,使用普遍存在的和成熟的高性能计算(HPC)编程框架,如OpenMP和MPI,串行算法可演变为可扩展、多线程、多处理实现,其在国防、航空航天和工业应用等计算密集型领域将会获得更广更深应用。
举个例子,我们计算pi的数值近似值,但我们不需要在这里讨论算法的细节。使用源代码分析器的好处之一是能够深入理解应用程序的性能,这可以在不需要深入理解底层数学的情况下提高性能。对于任何代码优化工作,在每个优化步骤中衡量性能和代码的正确性至关重要。拥有会产生错误答案的非常快速的代码是无用的。通过使用包含HPC调试器和源代码分析器的成熟的行业验证工具,该工作将大大简化。让我们开始使用源代码分析器来研究我们的单线程实现。从我们的运行总结中,我们看到该程序在30.6秒内执行,并且从主要活动图表中可以看出,100%的时间花费在单线程计算中,如图1所示。
图1:运行表显示100%的时间花费在单线程计算中
在分析器中,在每行旁边显示的是每行代码所用时间百分比的图表行。因此,从数据中我们可以直观地看到,我们应用程序中消耗的大量时间在68行的“for”循环中,如图2所示。
图2:大多数时间用于68行的“for”循环
此循环是OpenMP的理想选择,OpenMP是一种易于使用的基于指令的并行编程API。基于编译系统OpenMP可以通过提供抽象来隐藏创建线程、消除线程和管理工作同步的复杂性,从而大大简化数值算法的线程。此循环是OpenMP的理想选择,OpenMP是一种易于使用的基于指令的并行编程API。基于编译系统OpenMP可以通过提供抽象来隐藏创建线程、消除线程和管理工作同步的复杂性,从而大大简化数值算法的线程。
接下来,我们添加一个编译指令实现与第68行的“for”循环并行,如图3所示。那么编译器要做什么编译指令呢?“并行”关键字指示每个线程将同时执行相同的代码,而“for”关键字是一个工作共享结构,它将线程的循环迭代进行拆分。其中一项挑战是使循环迭代独立,以便指令可以以任意顺序安全执行。在我们的例子中,“和”是循环迭代之间的依赖关系;这是一种被称为减少的常见情况。在这种情况下,本地副本由“和”组成,并且被初始化(“+”是表示初始值为0的运算符)。本地副本在各个线程上更新并在完成时合并为单个值以更新原始全局值。最后,个别关键字注明“x”是只在每个线程内可见的局部变量。
图3:编译指令被添加以实现与第68行的“for”循环并行
现在我们重新运行配置文件中的代码来量化优化工作并验证正确性,如图4所示。
图4:配置文件中的代码重新运行
从运行对话窗口,我们可以对使用两个线程的程序进行验证,总运行时间减少到19秒。将原始运行时间减少近一半,或者性能几乎翻倍,这是一个很好的开始。从“CPU浮点”图表中可以看出,总运行时间超过83%的用于执行浮点运算,这也是相当不错的。此外,在“应用程序活动”图表中,超过99%的计算时间花费在OpenMP部分,从而确认编译指示的有效使用。这些强大的性能增益可以更有效地利用DSP板的处理能力,但是我们能否实现更高的性能增益?
通过组合多个进程和多个线程,我们可以进一步扩展应用程序。下一步是为并行编程添加通信协议,即消息传递接口(MPI)。MPI标准为点对点和群集通信定义了广泛的例程。有许多开源和MPI商业实现,以及绝大部分并行程序员都使用它。考虑到通信的灵活性,MPI特别擅长SIMD [单指令多数据]式算法。这些算法最适用于数据可以以常规形式分区并分布于多个处理器的情况。对于我们的应用程序,我们将修改“for”循环,以便每个进程将对迭代器的子集进行操作。修改后的“for”循环现在看起来像图5中的画面。
图5:修改“for”循环因此每个进程都将在迭代器的一个子集上运行
这个“for”循环将运行在多个进程中,每个进程都与迭代器的一个子集一起工作。对于这个测试,我们将运行两个进程,每个进程有两个线程,因此每个进程将在迭代域的一半上工作,如图6所示。
图6:该测试在两个进程上是运行
查看运行摘要对话窗口,我们看到应用程序确实分布在两个进程和两个线程中,总运行时间为17秒,如图7所示,一分钟——发生了什么?我们认为我们的算法具有很高的可扩展性,所以我们期望运行时间接近8秒,或者是单进程、双线程运行时间的一半)。让我们仔细看看源代码分析器的CPU时间指标。
图7:总运行时间为17秒,CPU时间运行是其一半
“CPU时间”应该接近100%,但是大约是50%。此外,“非自愿上下文切换”的数量非常大。大量的非自愿上下文切换可以用作处理器之间迁移进程的指示,而低CPU时间可以确认这一点。对于这个例子,我们使用MPI的MVAPICH2实现。让我们深入了解MVAPICH2的特定实现文档。它规定,如果程序结合了MPI和OpenMP(或另一种多线程技术),则需要禁用处理器关联。默认情况下启用,处理器关联(也称为CPU锁定)将所有线程绑定在同一个内核上运行,这就消除了之前运行中证明的多线程获得的好处。在MVAPICH2中,将环境变量MV2_ENABLE_AFFINITY设置为零禁用亲和性,如图8。
图8:设置环境变量MV2_ENABLE_AFFINITY为零禁用亲和性
禁用亲和性时,总运行时间在9秒钟内,更接近预期的时间。
这个简短的pi探索演示了使用系统方法和过程来执行代码优化的最佳实践。这些步骤可概括为:1)对预期性能进行假设;2)使用工具来量化性能;3)将假设与实际数据进行比较。如果你没有系统地量化你的性能,那么看起来即使是这个量级的数量很容易被忽略,而性能上的一个因子可能更容易被发现。
计算密集型国防、航空航天和工业应用将受益于多线程和多处理。柯蒂斯-怀特公司CHAMP-XD2OpenVPX板,带有一对温度可扩展的八核XEON-D处理器,就是这个例子中使用的电路板,如图9所示。上面的例子表明,相对较少的代码修改可以提高使用软件技术(如OpenMP和MPI)的串行数值算法的性能。它还显示了调试和性能测量工具对验证和验证性能的重要性。
图9:柯蒂斯-怀特公司CHAMP-XD2使用一对温度可扩展的八核XEON-D处理器
领取专属 10元无门槛券
私享最新 技术干货