并行高性能计算1为什么要并行计算?
1 为什么要并行计算?
本章包括
- 什么是并行计算,为什么并行计算越来越重要
- 现代硬件中存在哪些并行性
- 应用并行的重要性
- 利用并行性的软件方法
有许多挑战需要广泛而有效地利用计算资源。传统上,大多数要求性能的应用都属于科学领域。但人工智能(AI)和机器学习应用预计将成为大规模计算的主要用户,比如:
- 为特大火灾建模,以协助消防人员和帮助公众
- 为飓风引发的海啸和风暴潮建模(参见第 13 章中的简单海啸模型)
- 语音识别
- 病毒传播和疫苗开发建模
- 模拟几十年或几百年的气候条件
- 无人驾驶汽车技术的图像识别
- 为应急人员配备洪水等灾害的模拟运行系统
- 降低移动设备的功耗
利用本书介绍的技术,您将能够处理更大的问题和数据集,同时还能以十倍、百倍甚至千倍的速度运行仿真。典型应用使得当今计算机的大部分计算能力尚未得到开发。并行计算是释放计算机资源潜力的关键。那么,什么是并行计算,如何使用并行计算为应用程序增效?
并行计算是在同一时间内执行多个操作,它需要程序员付出一些努力。首先,您必须识别并揭示应用程序中的并行潜力。潜在的并行性或并发性意味着,在系统资源可用时,以任何顺序进行操作都是安全的。而且,并行计算还有一个额外的要求:这些操作必须同时进行。为此,您还必须适当利用资源来同时执行这些操作。
并行计算引入了串行世界中不存在的新问题。我们需要改变我们的思维过程,以适应并行执行的额外复杂性,但通过实践,这将成为我们的第二天性。本书将带您开始探索如何利用并行计算的强大功能。
生活中呈现了大量并行处理的实例,这些实例往往成为计算策略的基础。下图为超市收银线,其目标是让顾客快速支付所要购买的商品。这可以通过雇用多名收银员来一次处理一个顾客或为其结账来实现。在这种情况下,熟练的收银员可以更快地完成结账流程,这样顾客可以更快地离开。另一种策略是采用许多自助结账台,让顾客自行结账。这种策略对超市的人力资源要求较低,可以开辟更多的通道来处理顾客。顾客可能无法像训练有素的收银员那样高效地自助结账,但由于并行性增加,队伍缩短,也许有更多顾客可以快速结账。
我们通过开发算法来解决计算问题:一套实现预期结果的步骤。在超市的类比中,结账过程就是算法。在这种情况下,它包括从购物篮中卸下商品、扫描商品以获得价格,以及为商品付款。这种算法是有顺序的(或连续的);它必须遵循这一顺序。如果有成百上千的顾客需要执行这项任务,那么为众多顾客结账的算法就包含了可以利用的并行性。从理论上讲,结账流程中的任何两个客户之间都不存在依赖关系。通过使用多条结账线或自助结账站,超市可以利用并行性,从而提高顾客购买商品和离开商店的速度。在如何实现这种并行性的问题上,每一种选择都会带来不同的成本和收益。
定义:并行计算是在算法中识别和暴露并行性,在软件中表达并行性,并了解所选实施方案的成本、优势和局限性。
归根结底,并行计算关乎性能。这不仅包括速度,还包括问题的规模和能效。我们在本书中的目标是让你了解当前并行计算领域的广度,熟悉最常用的语言、技术和工具,这样你就可以满怀信心地处理并行计算项目。如何并行往往是在项目开始时做出的重要决定。合理的设计是迈向成功的重要一步。回避设计步骤可能会在日后出现问题。同样重要的是,要保持切合实际的期望,并了解可用资源和项目的性质。
本章的另一个目标是介绍并行计算中使用的术语。方法之一是在阅读本书时,将附录 C 中的术语表作为术语的快速参考。由于这一领域和技术是逐步发展起来的,因此并行领域中许多术语的使用往往不够严谨和准确。随着硬件和应用中并行性的复杂性不断增加,我们必须从一开始就使用清晰明确的术语。
欢迎来到并行计算的世界!随着深入研究,技术和方法会变得更加自然,你会发现它的强大魅力。您从未想过要尝试的问题也会变得司空见惯。
1.1 为什么要学习并行计算?
未来是并行的。由于处理器设计已达到微型化、时钟频率、功耗甚至发热的极限,串行性能的增长已趋于平稳。 下图显示了商品处理器的时钟频率(执行指令的速度)、功耗、计算内核(简称内核)数量和硬件性能随时间变化的趋势。
- 上图中的kOps应该是:Operations Per Second, 具体是如何衡量的不太清楚,请以下图为准。
参考:https://github.com/karlrupp/microprocessor-trend-data
- 晶体管数量(Transistors):这是衡量处理器集成度的指标,晶体管数量越多,处理器能实现的功能就越复杂。
- 单线程性能(Single-Thread Performance):用SpecINT分数表示,反映了处理器在执行单线程任务时的性能。SpecINT是一个常用的计算机系统基准测试套件,主要用于衡量计算机系统在整数运算方面的性能。
- 频率(Frequency):即处理器的工作频率,单位为MHz,表示处理器每秒执行指令的次数。
- 典型功耗(Typical Power):处理器在正常工作时的功耗,单位为瓦特。
- 逻辑核心数(Number of Logical Cores):一个处理器中逻辑核心的数量,表示处理器同时处理多个任务的能力。
2005 年,内核数量突然从单核增加到多核。与此同时,时钟频率和功耗趋于平稳。理论性能稳步提高,因为性能与时钟频率和内核数量的乘积成正比。这种向增加内核数量而非时钟频率的转变表明,只有通过并行计算才能实现中央处理器(CPU)的最理想性能。
现代消费级计算硬件配备了多个中央处理器和/或图形处理器,可同时处理多个指令集。这些小型系统的计算能力往往可以与二十年前的超级计算机相媲美。要充分利用计算资源(笔记本电脑、工作站、智能手机等),程序员就必须掌握编写并行应用程序的工具。你还必须了解可提高并行性的硬件特性。
由于并行硬件功能多种多样,这给程序员带来了新的复杂性。英特尔推出的超线程就是其中之一。硬件逻辑单元的两个指令队列交错工作,可以让一个物理内核在操作系统(OS)看来是两个内核。矢量处理器是 2000 年左右开始在商品处理器中出现的另一种硬件功能。这些处理器可同时执行多条指令。矢量处理器(也称为矢量单元)的位宽指定了同时执行的指令数量。因此,一个 256 位宽的矢量单元可以同时执行四条 64 位(双倍)或八条 32 位(单精度)指令。
以家用台式机中常见的配备超线程技术的 16 核 CPU 和 256 位宽向量单元为例。一个使用单核心且未进行矢量化处理的串行程序仅使用了该处理器理论处理能力的 0.8%!计算结果是
16 个内核 × 2 个超线程 × (256 位宽向量单元)/(64 位双) = 128 条并行路径
其中,1 条串行路径/128 条并行路径 = .008 或 0.8%。下图显示,这只是 CPU 总处理能力的一小部分。
串行应用程序只能访问 16 核 CPU 处理能力的 0.8%。
如本例所示,计算串行和并行性能的理论预期和实际预期是一项重要技能。我们将在第 3 章中对此进行更深入的讨论。
软件开发工具的一些改进有助于在我们的工具包中增加并行性,目前,研究界正在做更多的工作,但距离解决性能差距问题还有很长的路要走。这让我们这些软件开发人员承担了从新一代处理器中获得最大收益的重任。
遗憾的是,软件开发人员在适应计算能力的这一根本性变化方面已经滞后。此外,由于新的编程语言和应用编程接口(API)层出不穷,要将当前的应用程序过渡到使用现代并行架构可能令人望而生畏。但是,如果对自己的应用程序有很好的了解,能够看到并行性,并对可用工具有扎实的了解,就能获得巨大的收益。应用程序到底能获得什么样的好处呢?让我们来详细了解一下。
1.1.1 并行计算有哪些潜在优势?
并行计算可以缩短解决问题的时间,提高应用程序的能效,并使您能够在现有硬件上解决更大的问题。如今,并行计算不再是最大型计算系统的专属领域。现在,这项技术已经出现在每个人的台式机或笔记本电脑中,甚至出现在手持设备上。这使得每个软件开发人员都有可能在自己的本地系统上创建并行软件,从而大大增加了新应用的机会。
随着人们的兴趣从科学计算扩展到机器学习、大数据、计算机制图和消费应用,工业界和学术界的前沿研究揭示了并行计算的新领域。自动驾驶汽车、计算机视觉、语音识别和人工智能等新技术的出现,要求消费设备和开发领域都具备强大的计算能力,因为这些领域必须消耗和处理大量的训练数据集。而在科学计算领域,长期以来一直是并行计算的专属领域,现在也出现了令人兴奋的新可能性。远程传感器和手持设备的普及可以将数据输入更大、更逼真的计算中,从而更好地为自然灾害和人为灾难的决策提供信息,这就需要更广泛的数据。
必须记住,并行计算本身并不是目标。相反,目标是并行计算的结果:缩短运行时间、执行更大规模的计算或降低能耗。
- 使用更多计算核心缩短运行时间
缩短应用程序运行时间或加快速度通常被认为是并行计算的主要目标。事实上,这通常也是并行计算的最大影响。并行计算可以加快密集型计算、多媒体处理和大数据操作的速度,无论您的应用程序需要几天甚至几周的时间来处理,还是现在就需要实时处理结果。
过去,程序员会在串行优化上花费更多精力,以挤出几个百分比的改进。而现在,有可能通过多种途径实现数量级的改进。这就为探索可能的并行范例带来了新的问题--机会多于编程人力。但是,对应用程序的全面了解和对并行化机会的认识,可以引导您在缩短应用程序运行时间的道路上越走越远。
- 利用更多计算节点解决更大的问题
通过在应用程序中暴露并行性,您可以将问题的规模扩大到串行应用程序无法达到的尺寸。这是因为计算资源的数量决定了可以做什么,而暴露并行性可以让您在更大的资源上运行,从而提供以前从未考虑过的机会。更多的主内存、磁盘存储、网络带宽和磁盘带宽以及 CPU 使更大的规模成为可能。以前面提到的超市为例,并行化就相当于雇用更多的收银员或开设更多的自助结账通道,以处理数量越来越多的顾客。
- 少花钱多办事,提高能效
并行计算的新影响领域之一是能源效率。随着手持设备中并行资源的出现,并行化可以加快应用速度。这可以让设备更快地返回睡眠模式,并允许使用更慢但功耗更低的并行处理器。因此,将重量级多媒体应用程序转移到 GPU 上运行,可以对能效产生更显著的影响,同时还能大大提高性能。采用并行技术的最终结果是降低了功耗,延长了电池寿命,这在这一细分市场中具有强大的竞争优势。
能效非常重要的另一个领域是远程传感器、网络设备和运行中的现场部署设备,如远程气象站。通常情况下,由于没有大型电源,这些设备必须能够在资源较少的情况下以小型封装运行。并行化扩展了这些设备的功能,并将工作从中央计算系统中卸载下来,这就是边缘计算的发展趋势。将计算转移到网络边缘,可以在数据源处进行处理,将数据浓缩成更小的结果集,更容易通过网络发送。
如果不直接测量用电量,准确计算应用程序的能耗成本是一项挑战。不过,您可以用制造商的热设计功率乘以应用程序的运行时间和使用的处理器数量来估算成本。热设计功率是指典型运行负载下的能量消耗率。应用的能耗可通过以下公式估算
P = (N 个处理器) × (R 瓦特/处理器) × (T 小时)
其中,P 为能耗,N 为处理器数量,R 为热设计功率,T 为应用程序运行时间。
英特尔 16 核至强 E5-4660 处理器的热设计功率为 120 W。您的应用程序的预计能耗为
P = (20 个处理器) × (120 瓦/处理器) × (24 小时) = 57.60 千瓦时
一般来说,GPU 的热设计功率高于现代 CPU,但有可能缩短运行时间,或者只需要几个 GPU 就能获得相同的结果。可以使用与之前相同的公式,其中 N 现在被视为 GPU 的数量。
假设您已将应用程序移植到多 GPU 平台。现在,您可以在 24 小时内在四个英伟达™(NVIDIA®)Tesla V100 GPU 上运行应用程序!英伟达™(NVIDIA®)Tesla V100 GPU 的最大热设计功率为 300 W。
P = (4 个 GPU) × (300 瓦/GPU) × (24 小时) = 28.80 千瓦时
在此示例中,GPU 加速应用程序的运行能耗仅为纯 CPU 版本的一半。请注意,在这种情况下,尽管解决问题的时间保持不变,但能源消耗却减少了一半!
要通过 GPU 等加速器设备降低能耗成本,应用程序必须具备足够的并行性。这样才能有效利用设备上的资源。
- 并行计算可以降低成本
对于软件开发人员团队、软件用户和研究人员来说,实际货币成本正成为一个越来越明显的问题。随着应用程序和系统规模的扩大,我们需要对可用资源进行成本效益分析。例如,下一代大型高性能计算(HPC)系统的电力成本预计将是硬件购置成本的三倍。
使用成本也促使云计算成为一种替代方案,在学术界、初创企业和各行各业中被越来越多地采用。一般来说,云计算提供商按使用资源的类型和数量以及使用时间计费。虽然 GPU 的单位时间成本通常高于 CPU,但某些应用可以利用 GPU 加速器,从而充分缩短运行时间,降低 CPU 成本。
1.1.2 并行计算注意事项
并行计算不是万能的。许多应用程序既没有足够大的规模,也没有足够长的运行时间,因此不需要并行计算。有些应用程序甚至没有足够的内在并行性可利用。此外,将应用程序过渡到利用多核和多核(GPU)硬件需要专门的努力,这可能会暂时将注意力从直接的研究或产品目标上转移开。投入的时间和精力必须首先被认为是值得的。在使应用程序快速运行并扩展到更大的问题之前,更重要的是应用程序能够运行并生成所需的结果。
我们强烈建议您在启动并行计算项目时制定一个计划。重要的是要了解有哪些加速应用程序的选项,然后选择最适合您项目的选项。在此之后,对所涉及的工作量和潜在回报(美元成本、能耗、解决问题的时间以及其他重要指标)进行合理估算至关重要。在本章中,我们将开始向您传授有关并行计算项目前期决策的知识和技能。
参考资料
- 软件测试精品书籍文档下载持续更新 https://github.com/china-testing/python-testing-examples 请点赞,谢谢!
- 本文涉及的python测试开发库 谢谢点赞! https://github.com/china-testing/python_cn_resouce
- python精品书籍下载 https://github.com/china-testing/python_cn_resouce/blob/main/python_good_books.md
- Linux精品书籍下载 https://www.cnblogs.com/testing-/p/17438558.html
1.2 并行计算的基本规律
在串行计算中,所有操作都会随着时钟频率的增加而加快。相比之下,在并行计算中,我们需要考虑并修改我们的应用程序,以充分利用并行硬件。为什么并行的数量很重要?要理解这一点,让我们来看看并行计算定律。
1.2.1 并行计算的极限: 阿姆达尔定律
我们需要一种方法,根据并行代码的数量来计算计算的潜在加速度。这可以通过 Gene Amdahl 于 1967 年提出的阿姆达尔定律来实现。该定律描述了一个固定大小的问题随着处理器的增加而加速的情况。下式说明了这一点,其中 P 是代码的并行部分,S 是串行部分,即 P + S = 1,N 是处理器的数量:
阿姆达尔定律强调,无论我们将代码的并行部分处理得多快,都会受到串行部分的限制。这种固定大小问题的缩放被称为强缩放。固定大小问题的加速度是处理器数量的函数。图中各线表示算法 100% 并行时的理想速度,以及 90%、75% 和 50% 并行时的理想速度。阿姆达尔定律指出,速度提升受限于保持串行的代码分数。
定义:强扩展(Strong Scaling)表示在总规模固定的情况下,求解时间与处理器数量的关系。
1.2.2 突破并行限制:Gustafson-Barsis(古斯塔夫森) 定律
Gustafson 和 Barsis 于 1988 年指出,随着处理器数量的增加,并行代码运行的问题规模也会增大。这为我们提供了另一种计算应用程序潜在加速度的方法。如果问题的大小与处理器数量成比例增长,那么加速度现在可以表示为
SpeedUp(N ) = N - S * (N - 1)
其中,N 是处理器数量,S 是串行分数。其结果是,通过使用更多的处理器,可以在相同时间内解决更大的问题。这为利用并行性提供了更多机会。事实上,随着处理器数量的增加,问题的规模也会随之增大,这是因为应用程序用户希望受益的不仅仅是额外处理器的性能,还希望使用额外的内存。下图所示的这种运行时扩展称为弱扩展(Weak Scaling),又称弱缩放。
定义:弱缩放表示在每个处理器处理固定大小的问题时,解决问题的时间与处理器数量的关系。
下图直观地展示了强缩放和弱缩放的区别。弱缩放理论认为每个处理器上的网格大小应保持不变,这样可以充分利用额外处理器的资源。强缩放观点主要关注计算速度。在实际应用中,强缩放和弱缩放都很重要,因为它们可以解决不同的用户场景。
可扩展性一词通常指硬件或软件中是否可以增加更多并行性,以及改进的程度是否存在总体限制。虽然传统的重点是运行时扩展,但我们将提出内存扩展往往更为重要的论点。
下图显示了一个内存可扩展性有限的应用程序。复制阵列 (R) 是在所有处理器上复制的数据集。分布式数组 (D) 则是在处理器之间进行分区和分割。例如,在游戏模拟中,100 个字符可以分布在 4 个处理器上,每个处理器上有 25 个字符。但游戏棋盘的地图可能会复制到每个处理器上。复制的数组被复制到整个网格中。由于该图是弱扩展图,因此问题的大小会随着处理器数量的增加而增大。对于 4 个处理器,每个处理器上的数组是原来的 4 倍。随着处理器数量和问题规模的增加,很快就会出现处理器内存不足,作业无法运行的情况。有限的运行时间扩展意味着作业运行缓慢;有限的内存扩展意味着作业根本无法运行。还有一种情况是,如果应用程序的内存可以分配,运行时间通常也可以扩展。反之,则不一定。
对计算密集型作业的一种看法是,在每个处理周期中,内存的每个字节都会被触及,运行时间是内存大小的函数。减少内存大小必然会减少运行时间。因此,随着处理器数量的增加,并行化的最初重点应是减少内存大小。
1.3 并行计算如何运行?
并行计算需要结合对硬件、软件和并行性的理解来开发应用程序。它不仅仅是消息传递或线程。当前的硬件和软件提供了许多不同的选项,可为您的应用程序带来并行化。其中一些选项可以组合使用,以提高效率和速度。
重要的是,要了解应用程序中的并行化以及不同硬件组件允许您采用的并行化方式。此外,开发人员还需要认识到,在源代码和硬件之间,应用程序还必须穿越其他层,包括编译器和操作系统(。
作为开发人员,您负责应用软件层,其中包括您的源代码。在源代码中,你要选择编程语言和并行软件接口,以利用底层硬件。此外,你还要决定如何将工作分解成并行单元。编译器的作用是将源代码转换成硬件可以执行的形式。有了这些指令,操作系统就能在计算机硬件上执行这些指令。
我们将举例说明如何通过原型应用程序将并行化引入算法。这一过程发生在应用软件层,但需要对计算机硬件有所了解。目前,我们暂不讨论编译器和操作系统的选择。我们将逐步增加并行化的每一层,这样你就能看到它是如何工作的。对于每一种并行策略,我们都将解释可用硬件如何影响所做的选择。这样做的目的是展示硬件特性如何影响并行策略。我们将开发人员可以采取的并行方法分为以下几类
- 基于进程的并行化
- 基于线程的并行化
- 矢量化
- 流处理
我们将介绍一个模型,帮助您思考现代硬件。该模型将现代计算硬件分解为单个组件和各种计算设备。本章包含内存的简化视图。第 3 章和第 4 章将详细介绍内存层次结构。最后,我们将详细讨论应用层和软件层。
如前所述,我们将开发人员可采用的并行方法分为基于进程的并行化、基于线程的并行化、矢量化和流处理。基于单个进程的并行化有自己的内存空间,可以在计算机的不同节点上或在一个节点内使用分布式内存。流处理通常与 GPU 有关。现代硬件和应用软件模型将帮助您更好地理解如何计划将应用程序移植到当前的并行硬件。
1.3.1 浏览示例应用程序
在并行化介绍中,我们将探讨数据并行方法。这是最常见的并行计算应用策略之一。我们将在由矩形元素或单元组成的规则二维(2D)网格上执行计算。创建空间网格并为计算做准备的步骤(在此总结,稍后详述)是
- 将问题离散化(分解)为更小的单元或元素
- 定义对网格中每个元素进行计算的内核(操作)
- 在 CPU 和 GPU 上添加以下几层并行化,以执行计算:
- 矢量化-一次处理多个数据单元
- 线程-部署一个以上的计算通道,以调动更多处理核心
- 进程--将计算分散到不同内存空间的独立程序实例
- 将计算卸载到 GPU-将数据发送到图形处理器进行计算
我们从一个空间区域的二维问题域开始。为了便于说明,我们将以喀拉喀托火山的二维图像为例。我们计算的目标可能是模拟火山羽流、由此引发的海啸,或者利用机器学习来早期检测火山爆发。对于所有这些选项,如果我们希望获得实时结果,为决策提供依据,计算速度至关重要。
- 步骤 1:将问题离散化为更小的单元或元素
对于任何详细计算,我们都必须首先将问题域分解成更小的部分,这一过程称为离散化。在图像处理中,这通常只是位图图像中的像素。在计算域中,这些被称为单元或元素。单元或元素的集合构成计算网格,覆盖模拟的空间区域。每个单元的数据值可以是整数、浮点数或双倍。
上图将计算域离散为单元格。对于计算域中的每个单元,波高、流体速度或烟雾密度等属性都要根据物理规律进行求解。最终,模版运算或矩阵向量系统代表了这一离散方案。
- 第 2 步:定义对网格中每个元素进行计算的内核或运算
对离散数据的计算通常是某种形式的模版运算,所谓模版运算是因为它涉及相邻单元格的模式,以计算每个单元格的新值。这可以是平均运算(模糊运算,用于模糊图像或使图像更模糊)、梯度运算(边缘检测,用于使图像边缘更清晰),或者与求解偏微分方程(PDE)描述的物理系统相关的其他更复杂的运算。下图所示的模版操作是一个五点模版,通过使用模版值的加权平均值来执行模糊操作。
上图在计算网格上以十字图案表示的五点钢网运算。模版标记的数据在运算中读取并存储在中心单元格中。每个单元格都会重复这一模式。模糊算子是较简单的模版算子之一,它是用大圆点标记的五个点的加权和,并更新模版中心点的值。这类运算用于平滑运算或波传播数值模拟。
但这些偏微分方程是什么呢?让我们回到刚才的例子,想象一下这次是由独立的红、绿、蓝阵列组成 RGB 色彩模型的彩色图像。这里的 “偏 ”是指变量不止一个,我们要将红色随时间和空间的变化与绿色和蓝色的变化分开。然后,我们分别对这些颜色进行模糊运算。
还有一个要求:我们需要应用随时间和空间变化的速率。换句话说,红色会以一种速率扩散,而绿色和蓝色则以另一种速率扩散。这可能是为了在图像上产生特殊效果,也可能是为了描述真实色彩在显影过程中如何在摄影图像中渗出和融合。在科学界,我们可以用质量、X 和 Y 速度来代替红、绿和蓝。如果再加上一点物理知识,我们就可以得到波浪或烟灰羽流的运动。
- 第 3 步:矢量化,一次处理多个数据单元
我们先从矢量化开始介绍并行化。什么是矢量化?有些处理器具有同时对多个数据进行操作的能力;这种能力被称为矢量操作。下图中的阴影块说明了在处理器的矢量单元中,如何在一个时钟周期内用一条指令同时对多个数据值进行操作。
上图对四个二进制数进行特殊的矢量操作。该操作可在一个时钟周期内执行,几乎不会增加串行操作的能耗。
- 第 4 步:线程部署多个计算通路,以使用更多处理内核
由于目前大多数 CPU 至少有四个处理内核,因此我们使用线程同时操作内核,一次操作四行。图 1.12 展示了这一过程。
- 第 5 步:将计算分散到不同内存空间的进程
我们可以进一步在两个桌面上的处理器(并行处理中通常称为节点)之间拆分工作。当工作在节点间分割时,每个节点的内存空间都是不同的、独立的。如图所示,各行之间留有间隙。
上图通过将 4×4 块分配给不同的进程,该算法可以进一步并行化。每个进程使用四个线程,每个线程在一个时钟周期内处理一个四节点宽的向量单元。图中额外的空白区域说明了进程边界。
即使在硬件条件相当一般的情况下,速度也有可能提高 32 倍。具体表现如下:
2 个台式机(节点)×4 个内核×(256 位宽向量单元)/(64 位双)= 32 倍的潜在速度提升
如果我们看一下拥有 16 个节点、每个节点 36 个内核和 512 位矢量处理器的高端集群,潜在的理论速度提升是串行进程的 4608 倍:
16 个节点 × 36 个内核 × (512 位宽矢量单元)/(64 位双) = 4,608 倍的潜在速度提升
- 步骤 6: 将计算卸载至 GPU
GPU 是另一种提高并行化能力的硬件资源。有了 GPU,我们可以利用大量流式多处理器开展工作。下图显示了如何将工作分别分割成 8x8 块。根据英伟达™(NVIDIA®)Volta GPU 的硬件规格,这些磁贴可由分布在 84 个流多处理器上的 32 个双精度内核进行处理,这样就有 2,688 个双精度内核同时工作。如果在一个 16 节点的集群中,每个节点有一个 GPU,每个 GPU 有 2,688 个双精度流式多处理器,那么 16 个 GPU 就可以实现 43,008 路并行处理。
在 GPU 上,向量长度要比 CPU 上的大得多。在这里,8×8 块分布在 GPU 工作组中。
这些数字令人印象深刻,但此时此刻,我们必须承认实际的速度提升远远达不到这一全部潜力,从而降低期望值。我们现在面临的挑战是如何组织这种极端的、不同层次的并行化,以获得尽可能多的加速。
在这个高层次的应用演练中,我们忽略了很多重要的细节,这些细节我们将在后面的章节中介绍。但即使是这种名义上的细节,也突出了一些揭示算法并行化的策略。要为其他问题开发类似的策略,就必须了解现代硬件和软件。现在,我们将深入探讨当前的硬件和软件模型。这些概念模型是对现实世界中各种硬件的简化表示,以避免复杂性并保持对快速发展系统的通用性。
1.3.2 当今异构并行系统的硬件模型
为了建立对并行计算工作原理的基本理解,我们将解释当今硬件的组成部分。首先,被称为 DRAM 的动态随机存取存储器用于存储信息或数据。计算内核(简称内核)执行算术运算(加、减、乘、除),评估逻辑语句,并从 DRAM 中加载和存储数据。当对数据进行操作时,指令和数据从内存加载到内核,进行操作,然后存储回内存。现代 CPU(通常称为处理器)配备了多个内核,能够并行执行这些操作。此外,配备 GPU 等加速器硬件的系统也越来越常见。GPU 配备了数千个内核和独立于 CPU DRAM 的内存空间。
由一个(或两个)处理器、DRAM 和加速器组合而成的计算节点,可以指单个家用台式机或超级计算机中的 “机架”。计算节点之间可以通过一个或多个网络连接,有时也称为互连。从概念上讲,一个节点运行一个操作系统实例,负责管理和控制所有硬件资源。由于硬件正变得越来越复杂和异构,我们将从简化的系统组件模型开始,这样每个组件都会更加明显。
- 分布式内存架构:跨节点并行方法
分布式内存集群是并行计算最早也是最具扩展性的方法之一。每个 CPU 都有自己的由 DRAM 组成的本地内存,并通过通信网络与其他 CPU 相连。分布式内存集群的良好可扩展性源于其看似无限的集成更多节点的能力。
分布式内存架构连接了由独立内存空间组成的节点。这些节点可以是工作站或机架。
这种架构还通过将总的可寻址内存划分为每个节点的较小子空间来提供一定的内存局部性,这使得在节点外访问内存与在节点上访问内存明显不同。这就迫使程序员明确访问不同的内存区域。这样做的缺点是,程序员必须在应用程序一开始就管理内存空间的划分。
- 共享内存架构:节点上并行方法
另一种方法是将两个 CPU 直接连接到同一个共享内存。这种方法的优点是处理器共享相同的地址空间,从而简化了编程。但这会带来潜在的内存冲突,导致正确性和性能问题。在 CPU 或多核 CPU 的处理内核之间同步内存访问和内存值既复杂又昂贵。
上图:共享内存架构可在节点内实现并行化。
增加更多 CPU 和处理内核并不会增加应用程序可用的内存量。这一点以及同步成本限制了共享内存架构的可扩展性。
- 矢量单元:单指令多操作
为什么不能像过去那样提高处理器的时钟频率,以获得更大的吞吐量?提高 CPU 时钟频率的最大限制是需要更多的功率和产生更多的热量。无论是电力线安装受限的高性能计算超级计算中心,还是电池容量有限的手机,如今的设备都存在电力限制。这个问题被称为 “电源墙”。
与其提高时钟频率,为什么不在每个周期内多进行一次操作呢?这就是许多处理器重新采用矢量化技术背后的理念。与单个操作(更正式的说法是标量操作)相比,在矢量单元中进行多个操作只需要多耗费一点能量。通过矢量化,我们可以在一个时钟周期内处理比串行处理更多的数据。多操作(与单操作相比)对功耗的要求几乎没有变化,执行时间的缩短可以降低应用程序的能耗。与单车道公路相比,四车道高速公路可同时容纳四辆汽车行驶,矢量运算可提供更大的处理吞吐量。事实上,通过矢量单元的四条路径通常被称为矢量运算的车道。
大多数 CPU 和 GPU 都具备一定的矢量化或同等操作能力。一个时钟周期内处理的数据量,即矢量长度,取决于处理器上矢量单元的大小。目前,最常见的矢量长度为 256 位。如果离散数据是 64 位的二进制数,那么我们就可以同时进行四次浮点运算作为矢量操作。如下图,矢量硬件单元每次加载一个数据块,同时对数据执行单个操作,然后存储结果。
- 加速器设备: 专用附加处理器
加速器设备是为快速执行特定任务而设计的分立硬件。最常见的加速设备是 GPU。当用于计算时,这种设备有时被称为通用图形处理器(GPGPU)。GPU 包含许多小型处理内核,称为流式多处理器(SM)。虽然 SMs 比 CPU 内核简单,但却能提供巨大的处理能力。通常,CPU 上会集成一个小型 GPU。
大多数现代计算机还有一个独立的 GPU,通过外设组件接口(PCI)总线与 CPU 相连。这种总线带来了数据和指令的通信成本,但独立显卡通常比集成单元更强大。例如,在高端系统中,英伟达(NVIDIA)使用 NVLink,而 AMD Radeon 则使用 Infinity Fabric 来降低数据通信成本,但这一成本仍然很高。我们将在第 9-12 章详细讨论有趣的 GPU 架构。
GPU 分为集成式和分立式两种。分立或专用 GPU 通常拥有大量流式多处理器和自己的 DRAM。访问分立 GPU 上的数据需要通过 PCI 总线进行通信。
- 通用异构并行架构模型
现在,让我们将所有这些不同的硬件架构整合到一个模型中。两个节点各有两个 CPU,共享相同的 DRAM 内存。每个 CPU 都是集成了 GPU 的双核处理器。PCI 总线上的独立 GPU 也连接到其中一个 CPU。虽然 CPU 共享主内存,但它们通常位于不同的非统一内存访问(NUMA)区域。这意味着访问第二个 CPU 的内存比访问它自己的内存更昂贵。
上图为一般异构并行架构模型,由两个通过网络连接的节点组成。每个节点都有一个多核 CPU、一个集成和独立的 GPU 以及一些内存(DRAM)。现代计算硬件通常具有这些组件的某些排列方式。
在整个硬件讨论中,我们展示了一个简化的内存层次模型,只显示了 DRAM 或主存储器。我们在组合模型中显示了缓存,但没有详细说明其组成或功能。我们将在第 3 章讨论内存管理的复杂性,包括多级高速缓存。在本节中,我们只介绍了当今硬件的模型,以帮助您识别可用的组件,从而选择最适合您的 应用程序和硬件选择的并行策略。
1.3.3 当今异构并行系统的应用/软件模型
并行计算的软件模型必然受到底层硬件的影响,但又有别于硬件。操作系统提供了两者之间的接口。并行操作不会自行出现;相反,源代码必须指明如何通过生成进程或线程、将数据、工作和指令卸载到计算设备或同时对数据块进行操作来实现并行化。程序员必须首先暴露并行化,确定并行操作的最佳技术,然后以安全、正确和高效的方式明确指导其操作。以下方法是最常见的并行化技术,接下来我们将逐一详细介绍:
-
基于进程的并行化--消息传递
-
基于线程的并行化-通过内存共享数据
-
矢量化-用一条指令进行多个操作
-
流处理-通过专用处理器
-
基于进程的并行化:消息传递
消息传递方法是为分布式内存架构开发的,它使用显式消息在进程间移动数据。在这种模式下,应用程序会产生独立的进程,在消息传递中称为行列,它们拥有各自的内存空间和指令流水线。这些进程被交给操作系统,以便放置在处理器上。应用程序位于图中标注为用户空间的部分,用户在此拥有操作权限。下面的部分是内核空间,它受到保护,用户不能进行危险的操作。
上图为消息传递库产生进程。操作系统将进程放置在两个节点的内核上。问号表示操作系统控制进程的位置,并可在运行期间移动进程(如虚线箭头所示)。操作系统还会从节点的主内存中为每个进程分配内存。
请记住,处理器(CPU)具有多个处理内核,但这些内核并不等同于进程。进程是操作系统的概念,而处理器是硬件组件。无论应用程序产生多少进程,操作系统都会将这些进程调度到处理内核上。实际上,你可以在四核笔记本电脑上运行八个进程,而这些进程只会在处理内核之间交换。因此,我们开发了一些机制来告诉操作系统如何安排进程,以及是否将进程 “绑定 ”到处理核心。第 14 章将详细讨论如何控制绑定。
要在进程间移动数据,需要在应用程序中编写显式消息。这些消息可以通过网络或共享内存发送。1992 年,许多消息传递库合并为消息传递接口(MPI)标准。从那时起,MPI 就占据了这一利基市场,几乎所有超出单节点规模的并行应用中都有 MPI 的身影。当然,你也会发现 MPI 库有许多不同的实现方式。
有些并行应用程序使用一种较低级别的并行化方法,即分布式计算。我们将分布式计算定义为一组通过操作系统级调用进行合作的松散耦合进程。虽然分布式计算是并行计算的一个子集,但两者之间的区别非常重要。分布式计算应用的例子包括点对点网络、万维网和互联网邮件。搜索地外智慧(SETI@home)只是众多科学分布式计算应用的一个例子。
每个进程的位置通常位于一个单独的节点上,并通过操作系统使用远程过程调用(RPC)或网络协议创建。然后,各进程通过进程间通信(IPC)传递信息来交换信息。简单的并行应用程序通常使用分布式计算方法,但通常是通过 Python 等高级语言和专门的并行模块或库来实现。
- 基于线程的并行化:通过内存共享数据
基于线程的并行化方法在同一进程中产生独立的指令指针。因此,线程之间可以轻松共享部分进程内存。但这也带来了正确性和性能方面的隐患。程序员需要确定指令集和数据的哪些部分是独立的,可以支持线程。第 7 章将更详细地讨论这些注意事项,我们将在这一章中介绍领先的线程系统之一 OpenMP。OpenMP 提供了生成线程并在线程间分工的能力。
基于线程的并行化方法中产生线程的应用进程。线程仅限于节点域。问号表示操作系统决定线程的位置。线程之间共享部分内存。
线程方法有很多种,从重型到轻型不等,由用户空间或操作系统管理。虽然线程系统仅限于在单个节点内扩展,但对于适度提速而言,这些都是很有吸引力的选择。不过,单节点的内存限制会对应用程序产生较大影响。
- 矢量化:用一条指令执行多个操作
对应用程序进行矢量化处理远比在高性能计算中心扩展计算资源更具成本效益,而且这种方法在手机等便携设备上可能是绝对必要的。在进行矢量化时,工作以每次 2-16 个数据项的块为单位进行。这种操作分类的更正式术语是单指令多数据(SIMD)。在谈到向量化时,SIMD 这个术语被频繁使用。SIMD 只是并行架构的一个类别,稍后将在第 1.4 节中讨论。
从用户应用程序中调用矢量化通常是通过源代码实用程序或编译器分析实现的。实用程序和指令是给编译器的提示,用于指导如何并行化或矢量化一段代码。语法和编译器分析都高度依赖于编译器的能力。在这里,我们依赖于编译器,而以前的并行机制依赖于操作系统。此外,如果没有明确的编译器标志,生成的代码只能用于功能最弱的处理器和矢量长度,从而大大降低了矢量化的效果。有一些机制可以绕过编译器,但这些机制需要更多的编程工作,而且不具备可移植性。
- 通过专用处理器进行流处理
流处理是一个数据流概念,即由一个更简单的专用处理器处理数据流。该技术长期用于嵌入式计算,后被改用于在专用处理器(GPU)中为计算机显示器渲染大型几何对象集。这些 GPU 配备了一套广泛的算术运算和多个 SM,可并行处理几何数据。科学程序员很快就找到了将流式处理应用于细胞等大型模拟数据集的方法,从而将 GPU 的作用扩展为 GPGPU。
下图数据和内核通过 PCI 总线卸载到 GPU 进行计算。与 CPU 相比,GPU 的功能仍然有限,但在可以使用专用功能的情况下,它们能以较低的功耗提供非凡的计算能力。其他专用处理器也属于这一类,但我们主要讨论 GPU。
上图在流式处理方法中,数据和计算内核被卸载到 GPU 及其流式多处理器上。处理后的数据(或输出)被传输回 CPU,用于文件 IO 或其他工作。
1.4 并行方法的分类
如果您阅读更多有关并行计算的内容,您会遇到 SIMD(单指令、多数据)和 MIMD(多指令、多数据)等缩写词。这些术语指的是 Michael Flynn 于 1966 年提出的计算机体系结构类别,即所谓的 “Flynn 分类法”。这些类别有助于以不同的方式看待架构中潜在的并行化。分类的依据是将指令和数据分成串行或多重操作。请注意,尽管分类法很有用,但有些架构和算法并不完全符合某个类别。分类法的作用在于识别 SIMD 等类别中的模式,这些模式在使用条件时可能会遇到困难。这是因为每个数据项可能希望位于不同的代码块中,但线程必须执行相同的指令。
Flynn 分类法对不同的并行体系结构进行了分类。串行架构是单数据、单指令(SISD)架构。两类只有部分并行化,即指令或数据是并行的,但另一类是串行的。
如果有一个以上的指令序列,则称为多指令、单数据(MISD)。这种架构并不常见,最好的例子是对相同数据进行冗余计算。这种结构用于高度容错的方法,如航天器控制器。由于航天器处于高辐射环境中,这些控制器通常会对每项计算运行两份拷贝,并比较两者的输出结果。
矢量化是 SIMD 的一个典型例子,在这种情况下,同一指令可在多个数据中执行。SIMD 的一个变种是单指令多线程(SIMT),通常用于描述 GPU 工作组。
最后一类在指令和数据上都有并行化,被称为 MIMD。这一类描述的是多核并行架构,构成了大型并行系统的大部分。
1.5 并行策略
在 1.3.1 节的初始示例中,我们已经了解了单元或像素的数据并行化。但数据并行化也可用于粒子和其他数据对象。数据并行化是最常见的方法,通常也是最简单的方法。从本质上讲,每个进程执行的是相同的程序,但操作的是独特的数据子集,如下图右上方所示。数据并行化方法的优点是,随着问题规模和处理器数量的增加,数据并行化方法也能很好地扩展。
各种任务并行和数据并行策略,包括主工作站、流水线或水桶旅和数据并行
另一种方法是任务并行。这包括图 1.25 所示的带有工作线程的主控制器、流水线或桶状支队策略。流水线方法用于超标量处理器,在这种处理器中,地址和整数计算由单独的逻辑单元而不是浮点处理器完成,从而使这些计算可以并行进行。管道式处理器使用每个处理器对数据进行一系列操作和转换。在主处理程序方法中,一个处理器为所有处理程序安排和分配任务,每个处理程序在返回上一个已完成的任务时检查下一个工作项。还可以将不同的并行策略结合起来,以实现更大程度的并行。
1.6 并行速度与比较速度: 两种不同的衡量标准
我们将在本书中介绍大量的性能比较数据和加速度。通常情况下,“加速度 ”一词被用来比较两个不同的运行时间,但几乎没有任何解释或上下文,无法完全理解其含义。Speedup 是一个通用术语,在许多情况下都会使用,例如量化优化效果。为了澄清并行性能数据两大类之间的区别,我们将定义两个不同的术语。
-
并行速度提升--我们应该称之为串行到并行的速度提升。速度提升是相对于标准平台(通常是单 CPU)上的基准串行运行而言的。并行加速可能是由于在 GPU 上运行,或在计算机系统节点的所有内核上使用 OpenMP 或 MPI 运行。
-
Comparative speedup(比较加速度)--我们应该称之为架构间的比较加速度。这通常是两个并行实现之间的性能比较,或其他合理限制的硬件集之间的比较。例如,可能是计算机节点所有内核上的并行 MPI 实现与节点上的 GPU 之间的比较。
这两类性能比较代表了两个不同的目标。第一类目标是了解通过增加特定类型的并行性可以提高多少速度。然而,这并不是架构之间的公平比较。它涉及的是并行加速。例如,将 GPU 的运行时间与串行 CPU 的运行时间进行比较,并不能在多核 CPU 和 GPU 之间进行公平比较。当试图将一个节点上的多核 CPU 与一个或多个 GPU 的性能进行比较时,比较不同架构之间的加速度更为合适。
近年来,一些人已经将两种架构标准化,以便在类似的功率或能耗要求下比较相对性能,而不是在任意节点上进行比较。尽管如此,有如此多不同的架构和可能的组合,以至于可以获得任何性能数字来证明结论的正确性。您可以选择速度快的 GPU 和速度慢的 CPU,也可以选择四核 CPU 和 16 核处理器。因此,我们建议您在性能比较的括号中添加以下术语,以便为这些比较提供更多背景信息:
在每个术语前添加(2016 年最佳)。例如,并行加速度(2016 年最佳)和比较加速度(2016 年最佳)表示比较的是特定年份(本例中为 2016 年)发布的最佳硬件,您可能会将高端 GPU 与高端 CPU 进行比较。
如果两种架构在 2016 年发布,但不是最高端的硬件,则添加(Common 2016)或(2016)。这可能与开发人员和用户有关,他们拥有比高端系统更主流的部件。
如果 GPU 和 CPU 是在 2016 年发布的 Mac 笔记本电脑或台式机中使用,则添加 (Mac 2016);如果其他品牌在一段时间内(本例中为 2016 年)使用固定组件,则添加类似内容。这种类型的性能比较对于使用常见系统的用户很有价值。
添加(GPU 2016:CPU 2013)以表明所比较的组件的硬件发布年份(本例中为 2016 年和 2013 年)可能不匹配。
不对比较数字进行限定。谁知道这些数字意味着什么?
由于 CPU 和 GPU 型号的激增,性能数字必然更像是苹果和橘子之间的比较,而不是一个明确定义的指标。但在比较正式的场合,我们至少应该说明比较的性质,以便其他人更好地理解数字的含义,并对硬件供应商更加公平。
1.7 你将从本书中学到什么?
本书是为应用程序代码开发人员编写的,并不假定您以前有并行计算方面的知识。您只需具备提高应用程序性能和可扩展性的愿望即可。应用领域包括科学计算、机器学习和大数据分析,系统范围从台式机到最大的超级计算机。
要从本书中充分受益,读者应该是熟练的程序员,最好使用 C、C++ 或 Fortran 等编译过的 HPC 语言。我们还假定读者对硬件架构有初步了解。此外,读者应熟悉计算机技术术语,如比特、字节、操作、缓存、内存等。对操作系统的功能以及它如何管理和连接硬件组件也有基本的了解。阅读本书后,你将获得的一些技能包括
- 确定何时使用消息传递(MPI)比使用线程(OpenMP)更合适,反之亦然
- 估算矢量化可能带来的速度提升
- 确定应用程序中哪些部分最有可能加速
- 决定何时利用 GPU 来加速您的应用程序可能是有益的
- 确定应用程序的峰值潜在性能
- 估算应用程序的能耗成本
即使读完第一章,您也应该对并行编程的不同方法感到得心应手。我们建议你通过每章的练习来帮助你整合我们介绍的众多概念。如果你开始对当前并行架构的复杂性感到不知所措,那么你并不孤单。要掌握所有的可能性是很有挑战性的。在接下来的章节中,我们将逐一分解,让您更容易理解。
1.7.1 补充阅读
在劳伦斯利弗莫尔国家实验室网站上可以找到并行计算的基本介绍:https://hpc.llnl.gov/documentation/tutorials/introduction-parallel-computing-tutorial
1.7.2 练习
-
日常生活中还有哪些并行操作的例子?你会如何对你的例子进行分类?并行设计似乎是为了优化什么?你能计算出这个例子的并行加速吗?
-
对于您的台式机、笔记本电脑或手机,与串行处理能力相比,您系统的理论并行处理能力是多少?其中有哪些并行硬件?
-
商店结账示例中,您看到了哪些并行策略?是否有一些并行策略没有显示出来?练习 1 中的示例如何?
-
您有一个图像处理应用程序,每天需要处理 1,000 张图像,每张图像的大小为 4 mebibytes (MiB, 220 或 1,048,576 字节)。处理每幅图像需要 10 分钟。您的集群由多核节点组成,每个节点有 16 个内核和总共 16 吉字节(GiB,230 字节或 1024 兆字节)的主内存存储空间。(请注意,我们使用的是正确的二进制术语 MiB 和 GiB,而不是 MB 和 GB,后者分别是 106 和 109 字节的公制术语)。
- 哪种并行处理设计能最好地处理这种工作量?
- 现在客户的需求增加了 10 倍。你的设计能处理好吗?需要做哪些更改?
-
英特尔至强 E5-4660 处理器的热设计功率为 130 W;这是使用全部 16 个内核时的平均功耗。英伟达™(NVIDIA®)的 Tesla V100 GPU 和 AMD 的 MI25 Radeon GPU 的热设计功率为 300 W。与 16 核 CPU 应用程序相比,您的应用程序在 GPU 上的运行速度应该快多少才能被认为更节能?
1.8 小结
- 在这个时代,硬件的大部分计算能力都只能通过并行化来实现,因此程序员应该精通并行化开发技术。
- 应用程序必须有并行工作。并行程序员最重要的工作就是揭示更多的并行性。
- 硬件的改进几乎可以增强所有的并行组件。依赖串行性能的提高不会带来未来的加速。提高应用性能的关键将全部在并行领域。
- 各种并行软件语言不断涌现,以帮助访问硬件功能。程序员应了解哪些语言适用于不同情况。