原文出处:并行计算简介
并行计算简介
(本人刚刚完成这篇长文章的翻译,尚未认真校对。若里面有翻译错误和打字错误敬请谅解,并请参考原贴)
1 摘要
最近项目需要实现程序的并行化,刚好借着翻译这篇帖子的机会,了解和熟悉并行计算的基本概念和程序设计。帖子的原文见这里。
这篇帖子旨在为并行计算这一广泛而宏大的话题提供一个非常快速的概述,作为随后教程的先导。因此,它只涵盖了并行计算的基础知识,实用于刚刚开始熟悉该主题的初学者。我们并不会深入讨论并行计算,因为这将花费大量的时间。本教程从对并行计算是什么以及如何使用开始,讨论与其相关的概念和术语,然后解析并行内存架构(parallel memory architecture)以及编程模型(programming models)等。这些主题之后是一系列关于设计和运行并行计算程序的复杂问题的实践讨论。最后,本教程给出了几个并行化简单串行程序的示例。
2 概述
2.1 什么是并行计算?
串行计算: 传统的软件通常被设计成为串行计算模式,具有如下特点:
- 一个问题被分解成为一系列离散的指令;
- 这些指令被顺次执行;
- 所有指令均在一个处理器上被执行;
- 在任何时刻,最多只有一个指令能够被执行。
例如,
并行计算: 简单来讲,并行计算就是同时使用多个计算资源来解决一个计算问题:
- 一个问题被分解成为一系列可以并发执行的离散部分;
- 每个部分可以进一步被分解成为一系列离散指令;
- 来自每个部分的指令可以在不同的处理器上被同时执行;
- 需要一个总体的控制/协作机制来负责对不同部分的执行情况进行调度。
例如,
这里的 计算问题 需要具有如下特点:
- 能够被分解成为并发执行离散片段;
- 不同的离散片段能够被在任意时刻执行;
- 采用多个计算资源的花费时间要小于采用单个计算资源所花费的时间。
这里的 计算资源 通常包括:
- 具有多处理器/多核(multiple processors/cores)的计算机;
- 任意数量的被连接在一起的计算机。
并行计算机:
通常来讲,从硬件的角度来讲,当前所有的单机都可以被认为是并行的:
- 多功能单元(L1缓存,L2缓存,分支,预取,解码,浮点数,图形处理器,整数等)
- 多执行单元/内核
- 多硬件线程
IBM BG/Q Compute Chip with 18 cores (PU) and 16 L2 Cache units (L2)
通过网络连接起来的多个单机也可以形成更大的并行计算机集群:
例如,下面的图解就显示了一个典型的LLNL并行计算机集群:
- 每个计算结点就是一个多处理器的并行计算机;
- 多个计算结点用无限宽带网络连接起来;
- 某些特殊的结点(通常也是多处理器单机)被用来执行特定的任务。
2.2 为什么要并行计算?
真实世界就是高度并行的:
- 自然界中的万事万物都在并发的,按照其内在时间序列运行着;
- 和串行计算相比,并行计算更适用于对现实世界中的复杂现象进行建模,模拟和理解;
- 例如,可以想象对这些进行顺序建模:
主要理由:
- 节约时间和成本:1)理论上来讲,在一个任务上投入更多的资源有利于缩短其完成时间,从而降低成本;2)并行计算机可以由大量廉价的单机构成,从而节约成本。
- 解决更大规模更复杂的问题:1)很多问题的规模和复杂度使得其难以在一个单机上面完成;2)一个有趣的例子:(Grand Challenge Problems)。3)网页搜索引擎/数据库每秒处理百万级别的吞吐量。
- 提供并发性:1)单个计算资源某个时间段只能做一件事情,而多计算资源则可以同时做多件事情;2)协同网络可以使得来自世界不同地区的人同时虚拟地沟通。
- 利用非局部的资源:1)可以利用更广范围中的网络资源;2)[email protected]的例子;以及3)[email protected]的例子。
- 更好地利用并行硬件:1)现代计算机甚至笔记本电脑在架构上都属于多处理器/多核的;2)并行软件已经适用于多核的并行硬件条件,例如线程等;3)在大多数情况下,运行在现代计算机上的串行程序实际上浪费了大量的计算资源。
并行计算的未来:
- 在过去的二十多年中,快速发展的网络,分布式系统以及多处理器计算机架构(甚至在桌面机级别上)表明并行化才是计算的未来;
- 在同一时期,超级计算机的性能已经有了至少50万倍的增加,而且目前还没有达到极限的迹象;
- 目前的峰值计算速度已经达到了10181018/秒
2.3 谁都在使用并行计算?
科学界和工程界:
从历史上来讲,并行计算就被认为是“计算的高端”,许多科学和工程领域的研究团队在对很多领域的问题建模上都采用了并行计算这一模式,包括:大气与地球环境、应用物理、生物科学、遗传学、化学、分子科学、机械工程、电气工程、计算机科学、数学、国防和武器研发等。
工业界和商业界:
如今,商业应用为更快速计算机的研发提供了更强劲的动力。这些商业应用程序需要以更复杂的方式处理大量数据,例如:大数据、数据库、数据挖掘、石油勘探、网页搜索引擎、基于web的商业服务、医学成像和诊断、跨国公司管理、高级图形学技术以及虚拟现实、网络视频和多媒体技术、协同工作环境等。
全球应用:
并行计算目前在实际上被广泛采用于大量应用中
3 概念和术语
3.1 冯诺依曼体系结构
以匈牙利数学家约翰·冯诺依曼命名的这一计算机体系结构,出现在他1945年发表的一篇论文中。这也通常被称为“存储程序计算机”——程序指令和数据都被保存在存储器中,这与早期通过“硬接线”编程的计算机不同。从此以后,所有的计算机走遵从这一基本架构:
- 四个组成部分:1)内存;2)控制器;3)处理器;4)输入输出。
- 读写操作:支持随机存储的内存用来同时保存程序指令和数据:1)程序指令用来指导计算机操作;2)数据是程序用来操作的对象。
- 控制器:从内存中读取指令或者数据,对这些指令进行解码并且顺序执行这些指令。
- 处理器:提供基本的算术和逻辑操作。
- 输入输出设备:是人机交互的接口。
那么冯诺依曼体系结构和并行计算有什么关系呢?答案是:并行计算机仍然遵从这一基本架构,只是处理单元多于一个而已,其它的基本架构完全保持不变。
3.2 弗林的经典分类
有不同的方法对并行计算机进行分类(具体例子可参见并行计算分类)。
一种被广泛采用的分类被称为弗林经典分类,诞生于1966年。弗林分类法从指令流和数据流两个维度区分多处理器计算机体系结构。每个维度有且仅有两个状态:单个或者多个。
下面个矩阵定义了弗林分类的四个可能状态:
单指令单数据(SISD): SISD是标准意义上的串行机,具有如下特点:1)单指令:在每一个时钟周期内,CPU只能执行一个指令流;2)单数据:在每一个时钟周期内,输入设备只能输入一个数据流;3)执行结果是确定的。这是最古老的一种计算机类型。
单指令多数据(SIMD): SIMD属于一种类型的并行计算机,具有如下特点:1)单指令:所有处理单元在任何一个时钟周期内都执行同一条指令;2)多数据:每个处理单元可以处理不同的数据元素;3)非常适合于处理高度有序的任务,例如图形/图像处理;4)同步(锁步)及确定性执行;5)两个主要类型:处理器阵列和矢量管道。
**多指令单数据(MISD):**MISD属于一种类型的并行计算机,具有如下特点:1)多指令:不同的处理单元可以独立地执行不同的指令流;2)单数据:不同的处理单元接收的是同一单数据流。这种架构理论上是有的,但是工业实践中这种机型非常少。
多指令多数据(MIMD): MIMD属于最常见的一种类型的并行计算机,具有如下特点:1)多指令:不同的处理器可以在同一时刻处理不同的指令流;2)多数据:不同的处理器可以在同一时刻处理不同的数据;3)执行可以是同步的,也可以是异步的,可以是确定性的,也可以是不确定性的。这是目前主流的计算机架构类型,目前的超级计算机、并行计算机集群系统,网格,多处理器计算机,多核计算机等都属于这种类型。值得注意的是,许多MIMD类型的架构中实际也可能包括SIMD的子架构。
3.3 一些常见的并行计算术语
和其它一些领域一样,并行计算也有自己的“术语”。下面列出了与并行计算相关联的一些常用术语,其中大部分术语我们在后面还会进行更详细的讨论。
- 结点(Node): 也就是一个独立的“计算机单元”。通常由多个CPU处理器/处理内核,内存,网络接口等组成。结点联网在一起以构成超级计算机。
- 中央处理器/套接字/处理器/核(CPU / Socket / Processor / Core): 这些术语也取决于我们讨论的语境。在过去,中央处理器通常是计算机中的一个单个执行单元。之后多处理器被植入到一个结点中。接着处理器又被设计成为多核,每个核成为一个独立的处理单元。具有多核的中央处理器有时候又被称为“套接字”——实际上也没有统一标准。所以目前来讲,我们称一个结点上具有多个中央处理器,每个中央处理器上又具有多个内核。
- 任务(Task): 任务通常是指一个逻辑上离散的计算工作部分。一个任务通常是一段程序或者一段类似于程序的指令集合,可以由一个处理器进行处理。一个并行程序通常由多个任务构成,并且可以运行在多个处理器上。
- 流水线(Pipelining): 可以将任务分解成为不同的步骤,并且由不同的处理单元完成,里面有输入流通过。这非常类似于一个装配线,属于一种类型的并行计算。
- 共享内存(Shared Memory): 从严格的硬件角度来讲,共享内存描述了一种计算机架构,其中所有的处理器都可以对共同的物理内存进行直接存取(通常是通过总线)。从编程的角度来讲,共享内存描述了一种模型,其中所有的并行任务都具有同一内存形态,并且都可以直接对同一内存区域进行直接定位和存取,而无论该物理内存实际上在哪里(也许在千里之外的另外一个计算机上?)。
- 对称多处理器(Symmetric Multi-Processor (SMP)): 属于一种共享内存的硬件架构,并且不同的处理器对内存以及其它资源都具有同等的访问权限(个人理解,就是不同处理器在角色上没有任何区别)。
- 分布式内存(Distributed Memory): 在硬件中,表示基于网络的内存存取方式;在编程模型中,表示任务仅仅能够从逻辑上“看到”本机上的内存,但是在其它任务执行的时候,必须通过通讯才能对其它任务所运行的机器上的内存进行存取。
- 通讯(communications): 并行任务通常需要数据交换。实现数据交换的方式有多种,例如通过共享内存或者通过网络。但是通常意义上,数据交换指的就是通讯,而无论其实现方式。
- 同步(Synchronization): 指的是并行任务之间的实时协调,通常伴随着通讯(communication)。同步通常由在程序中设立同步点来实现,也就是说,在其它任务没有执行到这一同步点的时候,某一任务不能进一步执行后面的指令。同步通常涉及到需要等待其它任务的完成,因此有时候会增加并行程序的执行时间。
- 粒度(Granularity): 在并行计算中,粒度定量地描述了计算与通讯的比率。粗粒度表示在通讯过程中需要做大量的计算性工作;细粒度则表示在通讯过程中需要做的计算性工作并不多。
- 加速比(Observed Speedup): 这是检测并行计算性能的最简单并且最被广泛使用的度量策略,其定义如下:串行计算的时钟周期数并行计算的时钟周期数串行计算的时钟周期数并行计算的时钟周期数。
- 并行开销(Parallel Overhead): 指的是相对于做实际计算,做协调并行任务所需要花费的时间总数。影响并行开销的因素主要包括:1)任务启动时间;2)同步;3)数据通讯;4)由并行语言,链接库,操作系统等因素而导致的软件开销;5)任务终止时间。
- 大规模并行(Massive Parallel): 指那些包含并行系统的硬件——拥有很多的处理元件。这里的“很多”可能会随着硬件条件的进步而不断增加,但目前,最大的并行系统所拥有的处理元件高达上百万件。
- 尴尬并行(Embarrassingly Parallel): 指的是同时解决很多类似而又独立的任务,其中任务之间几乎没有需要协调的地方。
- 可扩展性(Scalability): 指的是并行系统(包括软件和硬件)通过添加更多资源来成比例增加并行速度的能力。影响可扩展性的因素主要包括:1)硬件,尤其是内存-处理器带宽以及网络通讯的质量和速度;2)应用算法;3)相对并行开销;4)具体应用的特征。
3.4 并行程序的缺陷和代价
阿姆达尔定律: 阿姆达尔定律说一个程序的加速比潜力由其可以并行的部分所占的比例而决定,即:
speedup=11−Pspeedup=11−P.
如果没有代码可以被并行,那么p = 0,所以加速比为1。如果所有的代码都可以被并行,那么 p = 1,加速比为无穷大(当然只是理论上而言)。如果50%的代码可以被并行,那么最大的加速比为2,意味着的运行速度最快可以被加速到2倍。
如果引入并行程序中的处理器个数,则加速比可以被重新定义为:
speedup=1PN+S=11−P+PNspeedup=1PN+S=11−P+PN,
其中P仍然是并行代码所占的比例,N是处理器个数,S是串行代码所占比例(S = 1 - P)。
| N | P = 0.50 | p = 0.90 | p = 0.95 | p = 0.99 |
|---|---|---|---|---|
| 10 | 1.82 | 5.26 | 6.89 | 9.17 |
| 100 | 1.98 | 9.17 | 16.80 | 50.25 |
| 1000 | 1.99 | 9.91 | 19.62 | 90.99 |
| 10000 | 1.99 | 9.91 | 19.96 | 99。02 |
| 100000 | 1.99 | 9.99 | 19.99 | 99.90 |
之后我们就迅速意识到并行计算的极限所在,例如上表所示。
“注明引言:”你可以花费一生的时间使得你的代码的95%都可以被并行,然而你如论投入多少处理器,都不会获得20倍的加速比。
然而,某些问题我们可以通过增加问题的大小来提高其性能。例如:
| 类型 | 时间 | 比例 |
|---|---|---|
| 2D网格计算 | 85秒 | 85% |
| 串行比例 | 15秒 | 15% |
我们可以增加网格的维度,并且将时间步长减半。其结果是四倍的网格点数量,以及两倍的时间步长。之后的花费时间将变为:
| 类型 | 时间 | 比例 |
|---|---|---|
| 2D网格计算 | 680秒 | 97.84% |
| 串行比例 | 15秒 | 2.16% |
比起具有固定并行时间百分比的问题,那些可以随着问题规模增大而不断提高并行时间百分比的问题在并行化的意义上更具有可扩展性(复习一下可扩展性的定义^_^)。
复杂性: 通常而言,并行计算的程序要比相应的串行计算程序更加复杂,也许复杂一个量级。你不仅需要同时执行不同的指令流,而且需要注意他们之间数据的通信。复杂性通常由涉及软件开发周期各个方面的时间来确定,主要包括:1)设计;2)编码;3)调试;4)调参;5)维护。
遵循良好的软件开发实践对并行应用开发是至关重要的,尤其是在除你之外的其他人还需要和你合作的情况下。
可移植性: 由于一些API的标准化,例如MPI,POSIX线程以及OpenMP,并行程序的可移植性问题并不像过去那么严重,然而:
- 所有串行程序中所遇到的可移植性问题都会出现在相应的并行程序中。
- 尽管一些API已经被标准话,但是在一些细节的实现上任然有差异,有时候这些细节实现会影响到可移植性。
- 操作系统也会是导致可移植性的关键因素。
- 硬件架构的不同有时候也会影响到可移植性。
资源需求:
并行编程的主要目标就是降低时钟等待时间。然而要做到这一点,需要更多的CPU时间。例如,一个在8个处理器上跑了一个小时的并行程序实际上花费了8小时的CPU时间。
并行程序所需要的内存开销往往要大于相对应的串行程序,这是因为我们有时候需要复制数据,以满足库和子系统对并行算法的要求。
对于运行时间较短的并行陈顾,实际性能反而有可能比相对应的串行程序有所降低。这是由于并行环境的建立,任务创建,通讯,任务结束等在这个运行时间中有可能会占有比较大的比例。
可扩展性:
基于时间和解决方案的不同,可以将扩展性分为强可扩展性和弱可扩展性:
强可扩展性的特点是:1)在更多处理器被加入的时候,问题本身的规模不会增加;2)目标是将同一问题运行的更快;3)理想状态下,相比于对应的串行程序,运行时间为1/P。
弱可扩展性的特点是:1)随着更多处理器被加入,每个处理上需要处理的问题规模保持一致;2)目标是在相同的时间内解决更多规模的问题;3)理想状态下,在相同时间内,可以解决的问题规模增加为原问题规模的P倍。
并行程序的性能提高取决于一系列相互依赖的因素,简单地增加更多的处理器并不一定是唯一的方法。
此外,某些问题可能本身存在扩展性的极限,因此添加更多的资源有时候反而会降低性能。这种情形会出现在很多并行程序中。
硬件在可扩展性方面也扮演者重要角色,例如:1)内存-CPU之间的带宽;2)通讯网络的带宽;3)某个机器或者某个机器集合中的内存大小;4)时钟处理速度。
支持并行的库或者子系统同样也会限制并行程序的可扩展性。
还有更多内容敬请参考原文