在学习具体的CUDA编程之前,对一些文档中出现的概念性的词语不免有些疑惑,其中一个便是:Parallel Computing 并行计算。网上找了一些介绍的文章,发现美国Lawrence Livemore国家实验的官网上有一篇介绍并行计算的入门文章,原文地址:https://hpc.llnl.gov/training/tutorials/introduction-parallel-computing-tutorial。边读边做了一些翻译记录工作。
A. 并行计算概述
i. 什么是并行计算?
串行计算
从传统上说,软件是为串行计算而写的
- 问题被分解为一系列离散的指令
- 指令依次被执行
- 在一个单独的处理器上被执行
- 在任何时候都只有一个指令被执行
并行计算
简单来说,并行计算是同时使用多个计算资源来解决一个计算问题:
- 一个问题被分解成可以同时解决的离散部分
- 每个部分会被进一步细分为一系列的指令
- 每个部分的指令同时在不同的处理器上执行
- 采用一个整体的控制/协调机制
- 计算问题应该能够被:
- 分解成可以同时解决的分散工作
- 任何时候都可以执行多个指令
- 可以在短时间内使用多个计算资源解决
- 计算资源通常指:
- 一台具有多个处理器/核心的计算机
- 通过网络连接的任意数量的此类计算机
并行计算机
- 从硬件角度来看,当今几乎所有独立计算机都是并行的:
- 多个功能单元(L1缓存,L2缓存,分支,预取,解码单元,浮点计算单元,图形处理单元(GPU),整数处理单元等)
- 多种执行单元/核
- 多种硬件线程

- 网络连接多台独立计算机(节点)以组成更大的并行计算机集群
- 例如,下图显示了一个典型的LLNL并行计算机集群:
- 每个计算节点本身就是一台多处理器并行计算机
- 多个计算节点通过无限带宽网络连接
- 用于特殊目的的节点(可以是多处理器),也可用于其他目的
ii. 为什么要用并行计算?
- 现实事件是平行的
- 节约时间和金钱
- 解决更大更复杂的问题
- 提供保密性
- 利用非本地资源
- 更好地使用基础并行硬件
- 未来二十多年,从更快的网络,分布式系统和多处理器的计算架构(即便是桌面级的)更清晰了未来的趋势:并行计算就是计算的未来
- 在同一时间段内,超级计算机的性能提高了500,000倍以上,目前还看不到尽头
iii. 谁在使用并行计算
- 科学和工程(具体见原文)
- 工业和商业(具体见原文)
B. 概念和术语
i. 经典冯诺伊曼架构
- 以匈牙利数学家约翰·冯·诺依曼(John von Neumann)的名字命名,他在1945年的论文中首次提出了对电子计算机的一般要求
- 也称为“存储程序计算机”-程序指令和数据都保存在电子存储器中。与通过“hard wiring”进行编程的早期计算机不同。
- 从那以后所有的计算机都遵循这个架构:内存、控制单元、数学逻辑单元,输入/输出接口
ii. 弗林的古典分类学
- 有多种方法可以对并行计算机进行分类。自1966年以来一直使用的一种更广泛使用的分类称为Flynn's Taxonomy
- 弗林分类从指令流和数据流两个维度对多处理器进行分类,每个维度又从单处理器和多处理器两个方面进行划分
单指令单数据(SISD)
- 串行(非并行)计算机
- 单条指令:在任何一个时钟周期内,CPU仅作用一条指令流
- 单个数据:在任何一个时钟周期内,只有一个数据流被用作输入
- 确定性执行
单指令,多数据(SIMD)
- 一种并行计算机
- 一条指令:所有处理单元在任何给定的时钟周期执行同一条指令
- 多个数据:每个处理单元可以对不同的数据元素进行操作
- 最适合以高度规律性为特征的特殊问题,例如图形/图像处理。
- 同步(锁步)和确定性执行
- 两种:处理器阵列和矢量管道
多指令,单数据(MISD)
- 一种并行计算机
- 多指令:每个处理单元通过单独的指令流独立地对数据进行操作。
- 单个数据:单个数据流被馈送到多个处理单元中。
- 几乎没有此类并行计算机的实际示例。
多指令,多数据(MIMD)
- 一种并行计算机
- 多指令:每个处理器可能正在执行不同的指令流
- 多个数据:每个处理器可能正在使用不同的数据流
- 执行可以是同步或异步,确定性或非确定性
- 当前,最常见的并行计算机类型-大多数现代超级计算机都属于此类。
- 示例:最新的超级计算机,联网的并行计算机集群和“网格”,多处理器SMP计算机,多核PC。
- 许多MIMD架构还包括SIMD执行子组件
iii. 一些通用并行术语
- 超级计算/高性能计算 Supercomputing / High Performance computing (HPC)
- 节点 Node
- CPU / SOCKET / PROCESSOR / CORE
- 任务 Task:计算工作中的一个离散的逻辑部分
- 管道 Pipeline:将任务分解为由不同处理器单元执行的步骤,输入流通过,就像流水线一样
- 共享内存 Shared Memory:从硬件角度出发,描述了一种计算机体系结构,其中所有处理器都可以直接(通常基于总线)访问公共物理内存。从编程的角度讲,它描述了一个模型,其中并行任务都具有相同的“图片”内存,并且可以直接寻址和访问相同的逻辑内存位置,而不管物理内存实际存在于何处。
- 对称多处理器 Symmetric Multi-Processor (SMP):共享内存硬件体系结构,其中多个处理器共享一个地址空间,并且可以平等地访问所有资源。
- 分布式内存 Distributed Memory:
C. 并行计算机内存架构
i. 共享内存
一般特征
- 共享内存与平行计算的差别比较大,但是通常与全处理器作为全局地址空间来访问全部内存的方式有很大的功能相同点
- 多个处理器可以独立运行,但是共享相同的内存资源
- 一个处理器影响的内存位置更改对所有其他处理器可见
统一内存访问(UMA)
- 相关的处理器
- 平等的内存访问时间
- 有时被称作CC-UMA,一致性内存UMA。缓存融合意思是当一个处理器更新内存中的位置,所有其他处理器都会知道这个更新。缓存一致性在硬件级别实现
非统一内存访问(NUMA)
- 通常通过物理链接两个或多个SMP来完成
- 一个SMP可以直接访问另一个SMP的内存
- 并非所有处理器对所有内存的访问时间均相等
- 跨链接的内存访问速度较慢
优点
- 全局地址空间为存储器提供了用户友好的编程视角
- 由于内存接近CPU,因此任务之间的数据共享既快速又统一
缺点
- 内存和CPU之间缺乏可伸缩性
- 需要程序员负责同步的结构才能确保对全局内存的“正确”访问
ii.分布内存
一般特征
- 分布式内存系统需要通信网络来连接处理器间内存
- 处理器具有自己的本地内存
- 任务之间的同步同样是程序员的责任
- 尽管可以像以太网一样简单,但用于数据传输的网络“结构”却千差万别
优点
- 增加处理器数量,并且内存大小按比例增加
- 每个处理器都可以快速访问自己的内存,而不会产生干扰,也不会尝试保持全局缓存一致性而产生开销
- 成本效益:可以使用商业的,现成的处理器和网络
缺点
- 程序员负责与处理器之间的数据通信相关的许多细节
- 可能很难将基于全局内存的现有数据结构映射到此内存组织
- 内存访问时间不一致-与节点本地数据相比,驻留在远程节点上的数据需要更长的访问时间
iii. 混合分布式-共享内存
一般特征
- 当今世界上最大和最快的计算机采用共享和分布式内存体系结构
- 共享内存组件可以是共享内存机器和/或图形处理单元(GPU)
- 分布式内存组件是多个共享内存/ GPU机器的网络
- 在可预见的将来,这种类型的内存体系结构将继续占主导地位,并在计算的高端领域不断增加
优缺点
- 共享和分布式内存体系结构的共同点
- 增加可伸缩性是一个重要优势
- 程序员复杂性的增加是一个重要的缺点
D. 平行编程模型
i. 概述
有几种常用的并行编程模型:
- 共享内存
- 线程数
- 分布式内存/消息传递
- 数据并行
- 复合类型
- 单指令多数据(SPMD)
- 多指令多数据(MPMD)
并行编程模型作为硬件和内存体系结构之上的抽象而存在
这些模型并非特定于特定类型的计算机或内存体系结构:
- 分布式内存机器上的共享内存模型
Kendall Square Research(KSR)ALLCACHE方法。机器内存在物理上分布在联网机器上,但对用户而言却显示为单个共享内存的全局地址空间。通常,此方法称为“虚拟共享内存”。
- 共享内存机器上的分布式内存模型
SGI Origin 2000上的消息传递接口(MPI)。SGIOrigin 2000采用CC-NUMA类型的共享内存体系结构,其中每个任务都可以直接访问分布在所有计算机上的全局地址空间。但是,已经实现并普遍使用了使用MPI发送和接收消息的功能,就像在分布式存储机器的网络上通常所做的那样。
到底该使用哪种模型呢?这通常是现有资源和个人选择的结合。没有“最佳”模型,尽管某些模型肯定比其他模型有更好的实现。
以下各节描述了上述每个模型,并讨论了它们的一些实际实现。
ii. 共享内存模型
进程/任务共享一个公共地址空间,它们异步地对其进行读写。
各种机制(例如:锁、信号量)用于控制对共享内存的访问,解决争用并防止争用条件和死锁。
从编程的角度说,这种模型缺少数据的“所有者”概念,所以没必要显示的指定数据在任务之间的通讯。所有进程都可以看到共享存储并享有同等的访问权限。
在性能方面的一个重要缺点是,变得更加难以理解和管理数据局部性:
- 将数据保留在其上正在处理的进程本地,可以节省内存访问,当多个进程使用同一数据时就会发生缓存刷新和总线堵塞。
- 不幸的是,控制数据局部性很难理解,并且可能超出了普通用户的控制范围
实现
- 在独立的共享内存计算机上,本机操作系统,编译器和/或硬件为共享内存编程提供支持。例如,POSIX标准提供了一个使用共享内存的API,而UNIX提供了共享内存段(shmget,shmat,shmctl等)。
- 在分布式内存计算机上,内存在物理上分布在计算机网络中,但是通过专用的硬件和软件使其成为全局内存。可以使用多种SHMEM实现。
iii. 线程模型
此编程模型是共享存储器编程的一种。
在并行编程的线程模型中,单个“重负载”进程可以具有多个“轻负载”并发执行路径。
示例:
- a.out在本地系统运行,加载后申请所有必须的系统和用户资源来运行。这是一个“重量级”进程
- a.out执行一些串行的工作,同时创建一些能够被操作系统调用的任务(线程)
- 每个线程都有本地数据,但也共享a.out的全部资源。这样可以节省与为每个线程复制程序资源相关的开销。每个线程还受益于全局内存视图,因为它共享a.out的内存空间。
- 线程的工作最好被描述成主程序里的子程序,任何线程都可以在同一时刻与其他线程执行任何子程序。
- 线程通过全局内存(更新地址位置)相互通信。这需要同步结构,以确保多个线程不会在任何时候更新同一全局地址。
- 线程可以开始也可以结束,但是a.out仍然存在,以提供必要的共享资源,直到应用程序完成为止。
实现:
从编程的角度来看,线程实现通常包括:
- 在并行代码中调用的子程序的库
- 嵌入在串行或并行源代码中的一组编译器指令
- 无关的标准化工作导致了两种非常不同的线程实现:POSIX线程和OpenMP。
- 由IEEE POSIX 1003.1c标准(1995)指定。仅C语言。
- Unix / Linux操作系统的一部分
- 基于库
- 通常称为Pthreads。
- 非常明确的并行性;需要程序员对细节的高度关注。
- 行业标准,由一组主要的计算机硬件和软件供应商,组织和个人共同定义和认可。
- 基于编译器指令
- 便携式/多平台,包括Unix和Windows平台
- 在C / C ++和Fortran实现中可用
- 可以非常容易和简单地使用-提供“增量并行性”。可以以序列号开头。
- 其他线程实现是常见的:微软线程、Java,Python线程、GPU的CUDA线程
iv. 分布式模型 / 消息传递模型
该模型展示了以下特征:
- 一组在计算过程中使用自己的本地内存的任务。多个任务可以驻留在同一台物理计算机上和/或跨任意数量的计算机。
- 任务通过发送和接收消息通过通信交换数据。
- 数据传输通常需要每个流程执行协同操作。例如,发送操作必须具有匹配的接收操作。
典型实现:
- Message Passing Interface (MPI) :MPI是消息传递的“事实上”行业标准,实际上替代了用于生产工作的所有其他消息传递实现。 MPI实现几乎适用于所有流行的并行计算平台。并非所有实现都包含MPI-1,MPI-2或MPI-3中的所有内容。
v. 数据平行模型
- 也可以称为分区全局地址空间(PGAS:Partitioned Global Address Space)模型。
- 数据并行模型展示了以下特征:
- 地址空间被全局处理
- 大多数并行工作都集中在对数据集执行操作上。数据集通常组织成一个公共结构,例如数组或多维数据集。
- 一组任务共同在同一数据结构上工作,但是,每个任务在同一数据结构的不同分区上工作。
- 任务在其工作分区上执行相同的操作,例如,“向每个数组元素加4”。
- 在共享内存体系结构上,所有任务都可以通过全局内存访问数据结构。
- 在分布式内存体系结构上,全局数据结构可以在任务之间逻辑和(或)物理上拆分。
典型实现:
- Coarray Fortran:用于SPMD并行编程的Fortran 95的一小部分扩展。取决于编译器。
- 统一并行C(UPC):对SPMD并行编程的C编程语言的扩展。取决于编译器。
- 全局数组:在分布式数组数据结构的上下文中提供共享内存样式的编程环境。具有C和Fortran77绑定的公共域库。
- X10:IBM在Thomas J. Watson研究中心开发的基于PGAS的并行编程语言。
- Chapel:由Cray领导的开源并行编程语言项目。
vi. 混合模型
混合模型结合了多个以上描述的编程模型。
- 混合模型的常见示例是消息传递模型(MPI)与线程模型(OpenMP)的组合。
- 线程使用本地节点数据执行计算密集型内核
- 使用MPI通过网络在不同节点上的进程之间进行通信
- 这种混合模型非常适合集群多核/多核计算机的最流行(当前)硬件环境。
混合模型的另一个类似且越来越流行的示例是将MPI与CPU-GPU(图形处理单元)编程一起使用。
- MPI任务使用本地内存在CPU上运行,并通过网络相互通信。
- 计算密集型内核将卸载到节点上的GPU。
- 节点本地内存和GPU之间的数据交换使用CUDA(或类似方法)。
其他混合模型很常见:
- 带Pthread的MPI
- 具有非GPU加速器的MPI
- ...
vii. SPMD
- SPMD实际上是一个“高级”编程模型,可以基于前面提到的并行编程模型的任何组合来构建。
- 单个程序:所有任务同时执行同一程序的副本。该程序可以是线程,消息传递,并行数据或混合数据。
- 多个数据:所有任务可能使用不同的数据
- SPMD程序通常在其中编程有必要的逻辑,以允许不同的任务分支或有条件地仅执行被设计为执行的程序部分。也就是说,任务不一定必须执行整个程序-可能只是其中一部分。
- 使用消息传递或混合编程的SPMD模型可能是多节点群集最常用的并行编程模型。
viii. MPMD
- 像SPMD一样,MPMD实际上是一个“高级”编程模型,可以基于前面提到的并行编程模型的任何组合来构建。
- 多个程序:任务可以同时执行不同的程序。程序可以是线程,消息传递,并行数据或混合数据。
- 多个数据:所有任务可能使用不同的数据
- MPMD应用程序不像SPMD应用程序那样常见,但可能更适合于某些类型的问题,尤其是那些比域分解更能实现功能分解的问题。
最后一部分是设计并行程序(这部分更贴近编程,内容也比较多,放在下篇细解吧)