1. 并行计算-概述
基本术语:
- 结点(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)具体应用的特征。
1.1 并行计算
串行计算的特点:
- 一个问题被分解成为一系列离散的指令;
- 这些指令被顺次执行;
- 所有指令均在一个处理器上被执行;
- 在任何时刻,最多只有一个指令能够被执行。
![串行](http://img.e-com-net.com/image/info8/aee1dbbed6294c419c9a8162fa62b1a5.gif)
并行计算特点:
- 一个问题被分解成为一系列可以并发执行的离散部分;
- 每个部分可以进一步被分解成为一系列离散指令;
- 来自每个部分的指令可以在不同的处理器上被同时执行;
- 需要一个总体的控制/协作机制来负责对不同部分的执行情况进行调度。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cipXBuOF-1573295514820)(https://computing.llnl.gov/tutorials/parallel_comp/images/parallelProblem.gif)]
考虑并行计算的计算问题需要具有的特点:
- 能够被分解成为并发执行离散片段;
- 不同的离散片段能够被在任意时刻执行;
- 采用多个计算资源的花费时间要小于采用单个计算资源所花费的时间。
计算资源通常包括:
- 具有多处理器/多核(multiple processors/cores)的计算机; (单个计算机)
- 任意数量的被连接在一起的计算机。 (计算机群)
1.2 并行计算机
并行计算机即能在同一时间内执行多条指令(或处理多个数据)的计算机,并行计算机是并行计算的物理载体。并且, 从硬件的角度来讲,当前所有的单机都可以被认为是并行的 。
- 多功能单元(L1缓存,L2缓存,分支,预取,解码,浮点数,图形处理器,整数等)
- 多执行单元/内核
- 多硬件线程
冯诺依曼体系结构:
![冯诺依曼](http://img.e-com-net.com/image/info8/7c1fc7701ea54f5da877693dd81b3fb2.jpg)
该结构:
- 四个组成部分:1)内存;2)控制器;3)处理器;4)输入输出。
- 读写操作:支持随机存储的内存用来同时保存程序指令和数据:1)程序指令用来指导计算机操作;2)数据是程序用来操作的对象。
- 控制器:从内存中读取指令或者数据,对这些指令进行解码并且顺序执行这些指令。
- 处理器:提供基本的算术和逻辑操作。
- 输入输出设备:是人机交互的接口。
并行计算机的分类:
并行计算机可以分别根据指令与数据和存储方式进行分类
1.2.1 按照指令与数据
这是一种 被广泛采用的分类方法,被称为弗林经典分类,诞生于1966年。弗林分类法从指令流和数据流两个维度区分多处理器计算机体系结构。每个维度有且仅有两个状态:单个或者多个。
![f1](http://img.e-com-net.com/image/info8/ab9afe1443ec44d3835950c860434568.gif)
**单指令单数据(SISD):**SISD是标准意义上的串行机,具有如下特点 :
- 单指令:在每一个时钟周期内,CPU只能执行一个指令流;
- 单数据:在每一个时钟周期内,输入设备只能输入一个数据流;
- 执行结果是确定的。
单指令多数据(SIMD): SIMD属于一种并行计算机类型, 具有如下特点:
- 单指令:所有处理单元在任何一个时钟周期内都执行同一条指令;
- 多数据:每个处理单元可以处理不同的数据元素;
- 非常适合于处理高度有序的任务,例如图形/图像处理;
- 同步(锁步)及确定性执行;
- 两个主要类型:处理器阵列和矢量管道。
**多指令单数据(MISD):**MISD属于一种类型的并行计算机,具有如下特点:
- 多指令:不同的处理单元可以独立地执行不同的指令流;
- 单数据:不同的处理单元接收的是同一单数据流。
- 这种架构理论上是有的,但是工业实践中这种机型非常少。
**多指令多数据(MIMD):**MIMD属于最常见的一种类型的并行计算机,具有如下特点 :
- 多指令:不同的处理器可以在同一时刻处理不同的指令流;
- 多数据:不同的处理器可以在同一时刻处理不同的数据;
- 执行可以是同步的,也可以是异步的,可以是确定性的,也可以是不确定性的。
这是目前主流的计算机架构类型,目前的超级计算机、并行计算机集群系统,网格,多处理器计算机,多核计算机等都属于这种类型。值得注意的是,许多MIMD类型的架构中实际也可能包括SIMD的子架构。
1.2.2 按照存储方式
并行计算机的内存架构通常可以分为:共享内存、分布式内存和混合分布式—共享内存。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-56AG6ya2-1573295514822)(http://m.qpic.cn/psb?/V1152Co210wgAU/wCJxhzriZYDuJcQuFwvt8MsLkFEmnYiOqWknPNPv9c!/b/dDcBAAAAAAAA&bo=1gIMAgAAAAADBg!&rf=viewer_4&t=5)]
1.2.2.1 共享内存
一般特征:共享内存的并行计算机虽然也分很多种,但是通常而言,它们都可以让所有处理器以全局寻址的方式访问所有的内存空间。多个处理器可以独立地操作,但是它们共享同一片内存。一个处理器对内存地址的改变对其它处理器来说是可见的。根据内存访问时间,可以将已有的共享内存机器分为统一内存存取和非统一内存存取两种类型。
**统一内存存取(Uniform Memory Access):**目前更多地被称为对称多处理器机器(Symmetric Multiprocessor (SMP)),每个处理器都是相同的,并且其对内存的存取和存取之间都是无差别的。有时候也会被称为CC-UMA (Cache coherent - UMA)。缓存想干意味着如果一个处理器更新共享内存中的位置,则所有其它处理器都会了解该更新。缓存一致性是在硬件级别上实现的。
![p1](http://img.e-com-net.com/image/info8/ee3376ad27884b0a98f7df296770d6ee.jpg)
非统一内存存取(Non-Uniform Memory Access): 通常由两个或者多个物理上相连的SMP。一个SMP可以存取其它SMP上的内存。不是所有处理器对所有内存都具有相同的存取或者存取时间。通过连接而进行内存存取速度会更慢一些。如果缓存相缓存想干的特性在这里仍然被保持,那么也可以被称为CC-NUMA。
![p2](http://img.e-com-net.com/image/info8/5a85327379774d3a9c3af99a3a63ad45.gif)
优点: 全局地址空间提供了一种用户友好的编程方式,并且由于内存与CPU的阶级程度,使得任务之间的数据共享既快速又统一。
**缺点:**最大的缺点是内存和CPU之间缺少较好的可扩展性。增加更多的CPU意味着更加共享内存和缓存想干系统上的存取流量,从而几何级别地增加缓存/内存管理的工作量。同时也增加了程序员的责任,因为他需要确保全局内存“正确”的访问以及同步。
1.2.2.2 分布式内存
一般概念: 分布式内存架构也可以分为很多种,但是它们仍然有一些共同特征。分布式内存结构需要通讯网络,将不同的内存连接起来。一般而言,处理器会有它们所对应的内存。一个处理器所对应的内存地址不会映射到其它处理器上,所以在这种分布式内存架构中,不存在各个处理器所共享的全局内存地址。
![p3](http://img.e-com-net.com/image/info8/79687603803c461cac592ff9759ec056.jpg)
由于每个处理器具有它所对应的局部内存,所以它们可以独立进行操作。一个本地内存上所发生的变化并不会被其它处理器所知晓。因此,缓存想干的概念在分布式内存架构中并不存在。
如果一个处理器需要对其它处理器上的数据进行存取,那么往往程序员需要明确地定义数据通讯的时间和方式,任务之间的同步因此就成为程序员的职责。尽管分布式内存架构中用于数据传输的网络结构可以像以太网一样简单,但在实践中它们的变化往往也很大。
优点: 1)内存可以随着处理器的数量而扩展,增加处理器的数量的同时,内存的大小也在成比例地增加;2)每个处理器可以快速地访问自己的内存而不会受到干扰,并且没有维护全局告诉缓存一致性所带来的开销;3)成本效益:可以使用现有的处理器和网络。
缺点: 1)程序员需要负责处理器之间数据通讯相关的许多细节;2)将基于全局内存的现有数据结构映射到该分布式内存组织可能会存在困难;3)非均匀的内存访问时间——驻留在远程结点上的数据比本地结点上的数据需要长的多的访问时间。
1.2.2.3 混合分布式—共享内存
一般概念: 目前世界上最大和最快的并行计算机往往同时具有分布式和共享式的内存架构。共享式内存架构可以是共线内存机器或者图形处理单元(GPU)。分布式内存组件可以是由多个共享内存/GPU连接而成的系统。每个结点只知道自己的内存,不知道网络上其它结点的内存。因此,需要在不同的机器上通过网络进行数据通讯。从目前的趋势来看,这种混合式的内存架构将长期占有主导地位,并且成为高端计算在可见的未来中的最好选择。
优缺点: 1)继承了共享式内存和分布式内存的优缺点;2)优点之一是可扩展性;3)缺点之一是编程的复杂性。
1.3 并行计算模型
常见的并行编程模型包括:共享内存模型(无线程),线程模型,分布式内存/消息传递模型,数据并行模型,混合模型,单程序多数据模型,多程序多数据模型。
1.3.1 共享内存模型(无线程)
在这种并行计算模型中,处理器/任务共享内存空间,并且可以异步地对内存进行读写。很多机制被用来控制对内存的存取,例如锁/信号量等,用来解决访问冲突以及避免死锁。这应该是最简单的并行计算模型了 。
从编程者的角度来看,这种模型的好处之一数据“拥有者”的缺失,所以他们不必明确地指定数据之间的通讯。所有的处理器都可以看到和平等地存取共享内存。程序开发将因此而变得容易。
性能方面的一个重要缺点是对数据局部性的理解和管理讲变得困难:1)保持数据的局部性将有利于减少内存的存取负担,缓存刷新次数以及总线流量。而当多个处理器使用同一数据时,这些负担将会经常发生;2)不幸的是,保持数据的局部性往往比较难以理解,并且其难度超过一般用户的水平。
实现: 单机共享内存机器,本地操作系统,编译器及其对应的硬件都支持共享内存编程。在分布式内存机器上,内存在物理上存在于网络上不同的结点上,但是可以通过特殊的硬件和软件,将其变为全局可见。
1.3.2 线程模型
这是共享内存编程的一种模式。在线程模型中,一个单个的“重量级”进程可以拥有多个“轻量级”的并发执行路径。例如下图所示:
- 主程序 a.out 在本地操作系统上运行。a.out 需要加载所有的系统和用户资源来运行,这是里面的“重量级”进程。
- a.out 首先执行一些串行工作,然后生成一系列任务(线程),而这些线程可以在操作系统中被并发地执行。
- 每个线程具有本地数据,但同时也共享 a.out 的所有资源。这节约了所有线程都复制程序资源的的开销。而每个线程同时也从全局内存中获益,因为它可以共享 a.out 的内存空间。
- 一个线程的工作可以被描述为主程序的一个子程序。任何线程都可以在其它线程运行的同时执行任何子程序。
- 线程之间的通讯通过全局内存来实现(对全局地址的更新)。这需要建立一种同步机制,以保证在同一时刻,不会有多个线程对同一块地址空间进行更新。
- 线程可以随时生成和结束,但是 a.out 却一直存在,以提供所需的共享资源,直到整个应用程序结束。
![p4](http://img.e-com-net.com/image/info8/737011fa61474387aee14b770ab4d8a9.jpg)
实现: 从编程的角度来讲,线程的实现通常包括如下两个方面:
- 库函数或者子程序,这些库函数或者子程序可以在并行源代码中被调用;
- 嵌入在并行或者串行源代码中的一组编译器指令集合。
程序员需要同时定义上面的两个方面(尽管有时候编译器可以提供帮助)。
标准化工作却导致了两种完全不同的线程实现方式:POSIX Threads 和 OpenMP。
- POSIX Threads:由IEEE POSIX 1003.1c standard (1995)定义,仅支持C语言,是Unix/Linux操作系统的一部分,是基于库函数的,也通常被称为“Pthreads”。是非常明确的并行机制,需要程序员注意大量的细节。
- OpenMP:是一个工业标准,有一组主要计算机硬件和软件提供商,组织和个人联合发起和定义,是基于编译器指令的,具有可移植性/跨平台性,目前支持的包括Unix和Windows平台,目前支持C/C++和Fortran语言。非常简单和易用,提供了“增量并行”,可以从串行代码开始。
1.3.3 分布式内存/消息传递模型
这种模型具有如下特点:
- 在计算的过程中,每个任务都仅仅使用它们自身的本地内存。多个任务既可以寄宿在同一个物理机器上,也可以跨越不同的物理机器。
- 任务之间的数据交换是通过发送和接收消息而实现的。
- 数据传输通常需要不同进程之间的协同操作才能实现。例如,一个发送操作需要同时对应一个接收操作。
![p5](http://img.e-com-net.com/image/info8/bf60a776dffb406aa42a51e0489c4c5c.gif)
实现: 从编程的角度来讲,消息传递的实现通常包括子程序库。对这些子程序的调用往往就嵌入在源代码中。编程者负责并行机制的实现。 MPI成为了事实上的消息传递的工业标准,取代了所有其它消息传递的实现。几乎所有流行的并行计算平台都存在MPI的实现,但并不是所有的实现都包含了MPI-1,MPI-2和MPI-3的所有内容。
1.3.4 数据并行模型
通常也被称为“全局地址空间分区”(Partitioned Global Address Space (PGAS))模型。具有如下特点:
- 地址空间被认为是全局的。
- 大多数的并行工作聚焦于在数据集上的操作。数据集通常被组织成为常用的结构,例如数组,数立方等。
- 一系列任务在同一块数据结构上操作,但是每个任务却操作在该数据结构的不同分区上。
- 每个任务在数据结构的不同分区上执行相同的操作,例如,“给每个数组元素加上4”。
![p6](http://img.e-com-net.com/image/info8/d74bb553bac34505af44289e324de077.gif)
在共享内存的架构下,所有的任务通过全局内存方式来对数据进行存取;在分布式内存架构下,根据任务分配,全局数据结构在物理或者逻辑上被进行分割。
1.3.5 混合模型
混合模型指的是包含了至少两个我们前面提到的并行计算模型的模型。目前,最常见的混合模型的例子是消息传递模型(MPI)和线程模型(OpenMP)的结合:
- 线程使用本地数据完成计算密集型的任务;
- 不同的进程则在不同的结点上通过MPI完成数据通讯。
这种混合模型非常适合目前流行的硬件环境——多核计算机组成的集群系统。
![p7](http://img.e-com-net.com/image/info8/08fe8b73ceb0406a9389e49f69bcd913.gif)
另外一种类似的,但原来越流行的例子是采用MPI和CPU-GPU的混合编程:
- 采用MPI的任务运行于CPU上,使用本地内存上的数据,但是通过网络与其它任务进行数据交换;
- 而计算密集型的核则被加载到GPU上进行计算;
- 而结点内部的内存和GPU上的数据交换则通过CUDA(或者类似的东西)进行数据交换。
![p8](http://img.e-com-net.com/image/info8/33e92c87b31e4efe8f07427d020b0e77.gif)
其它混合模型还包括:
-
MPI和Pthreads的混合;
类似的,但原来越流行的例子是采用MPI和CPU-GPU的混合编程:
-
采用MPI的任务运行于CPU上,使用本地内存上的数据,但是通过网络与其它任务进行数据交换;
-
而计算密集型的核则被加载到GPU上进行计算;
-
而结点内部的内存和GPU上的数据交换则通过CUDA(或者类似的东西)进行数据交换。