01 / 导读
导读
引用
Pacheco, P. S., & Malensek, M. (2021). An Introduction to Parallel Programming (2nd ed.). Morgan Kaufmann (Elsevier).
章节概要
第二版约496页,共8章。第一版的三种编程接口(面向分布式内存的MPI,以及面向共享内存的Pthreads和OpenMP)之外,新增了一章讲用CUDA进行GPU编程。作者把本书定位为一本初级入门书,读者对象是能够在传统单处理器系统上编程、但几乎没有多处理器编程经验的学生和专业人员。出版社强调本书采用教程式写法,从小程序开始,逐步过渡到更难的例子,练习要求学生编译、运行和修改示例程序。作者提供下载的源代码附有一份索引,把每个程序与讨论它的小节一一对应,从中可以看出各章都是围绕少数几个在全书中反复出现的问题来组织的。
第1章:Why Parallel Computing(为什么需要并行计算)
开篇一章正如标题所言,阐明为什么需要并行计算,然后全书再转入硬件、软件和各个编程接口。
第2章:Parallel Hardware and Parallel Software(并行硬件与并行软件)
并行硬件和软件的背景知识。勘误表显示,硬件部分包括缓存一致性(2.3.5节),并附有作者的说明:由于总线不可扩展,监听式一致性如今已很少使用。代码索引表明,2.7.1节有一个串行的直方图程序,它是本章开发并行程序的示例。
第3章:Distributed Memory Programming with MPI(用MPI进行分布式内存编程)
从一个让每个进程输出问候语的程序开始,然后分几个版本并行化梯形积分法,从点对点消息逐步过渡到归约、广播等集合操作。后面各节讨论用scatter和gather分发向量、矩阵向量乘法、派生数据类型、计时与性能评估,以及并行奇偶交换排序。
第4章:Shared-Memory Programming with Pthreads(用Pthreads进行共享内存编程)
用问候程序和矩阵向量乘法引入线程,再借估算π的程序揭示竞争条件,并比较保护临界区的几种方法:忙等待、互斥量和信号量。进一步的话题包括用三种方式实现的路障、应用于共享链表的读写锁,以及线程安全——通过用一个非线程安全的库函数对文本做分词来说明。
第5章:Shared-Memory Programming with OpenMP(用OpenMP进行共享内存编程)
用临界区、reduction子句和parallel for循环重新处理梯形积分法;展示一个不能简单并行化的循环(斐波那契数);并讨论变量作用域、奇偶排序、循环调度、借助critical、atomic和锁在线程间传递消息、矩阵向量乘法、OpenMP任务以及线程安全。
第6章:GPU Programming with CUDA(用CUDA进行GPU编程)
本版新增。从以一个线程块启动的内核开始,接着是向量加法、从设备返回结果,以及三个逐步改进的CUDA版梯形积分法(据勘误表,6.11节讨论线程束洗牌函数),最后实现双调排序,先在单个线程块内,再跨越多个线程块。
第7章:Parallel Program Development(并行程序开发)
两个规模较大的案例研究,每个都用书中所有接口求解。n体问题求解器分基本版和简化版,分别用OpenMP、Pthreads、MPI和CUDA并行化;接着开发样本排序的串行版以及OpenMP、Pthreads、MPI和CUDA版本,其中MPI版本采用了不同的通信模式。
第8章:Where to Go from Here(何去何从)
最后一章正如标题所示,为进一步学习指明方向。
核心概念
第1–2章:基础
- 共享内存与分布式内存:并行系统的两大类,决定了进程是通过内存还是通过消息进行通信。
- 缓存一致性:使共享数据在不同核心缓存中的副本保持一致。
- 并行软件:被编写成以多个协作的进程或线程方式运行的程序。
第3章:MPI
- 点对点通信与集合通信:在两个进程之间发送消息,或在所有进程之间同时通信(reduce、broadcast、scatter、gather)。
- 数据分发:把向量和矩阵按块分配给各进程。
- 派生数据类型:描述不连续的数据,使之能够用一条消息发送。
- 性能评估:为并行程序计时并判断其可扩展性,这是3.6节的主题。
第4–5章:Pthreads与OpenMP
- 竞争条件与临界区:多个线程同时更新共享数据时产生的错误,以及必须加以保护的代码区域。
- 互斥量、信号量、路障、条件变量和读写锁:Pthreads的同步工具。
- 指令与子句:OpenMP并行化循环和代码区域的方式,包括归约与调度。
- 线程安全:函数在被多个线程同时调用时能否正确运行。
第6–7章:GPU与更大的程序
- 内核、线程与线程块:CUDA在GPU上运行大量以块组织的轻量级线程的模型。
- 线程束级操作:线程束洗牌等函数,使同一线程束中的线程能够高效交换数值。
- n体模拟与样本排序:用来在实际问题上比较全部四种接口的案例研究。
评析
优点
- 所有接口处理同样的问题。 梯形积分法出现在MPI、OpenMP和CUDA中;矩阵向量乘法和奇偶交换排序反复出现;第7章用全部四种接口求解n体问题和样本排序。学生可以直接比较各种编程模型,而不必面对互不相关的例子逐一学习。
- 从有缺陷的程序中学习。 代码索引显示出一种有意的安排:先给出有错误或过于简单的版本(易出现竞争的π估算、错误的斐波那契循环、非线程安全的分词),再加以修正,从而让学生明白同步工具为何存在。
- 实用且可运行。 作者提供全部源代码,并在网站上维护勘误表;出版社还为教师列出了讲义幻灯片、习题解答和图库。练习要求学生编译、运行和修改程序。
- 跟上GPU的发展。 新增的CUDA一章以及第7章中的CUDA版本,把加速器引入了一本原先只讲CPU和集群的教材。
局限
- 以C语言和API为中心。 所有程序都用C编写,本书讲授的是四种具体接口。它不涉及更高层的框架,也不讲超出示例所需范围的并行算法理论。
- GPU部分只讲CUDA。 使用其他加速器或可移植GPU框架的读者需要另找资料。出版社还提到异构编程,但目录中没有专门讨论它的章节,因此读者不应期待对这一主题有深入的论述。
- 不是分布式系统教材。 本书讲的是如何在多个处理器上让一项计算运行得更快;容错、一致性和复制这些本课程其他部分的核心内容都不在其范围之内。
- 部分硬件内容容易过时。 作者自己的勘误表指出,第2章关于监听式一致性的描述反映的是一种如今已很少使用的技术。
在课程中的定位
在分布式系统课程中,本书负责并行计算这一面:如何编写程序,利用多个核心、多个节点或GPU让一项计算更快地运行。其他几本书回答的是另一个问题:如何构建在不可靠的机器和网络之上仍能持续运行的服务。罗伯托·维蒂洛的 Understanding Distributed Systems 是面向开发者的简明入门,讲网络化服务中的通信、协调、扩展和弹性;库鲁里斯等人的 Distributed Systems: Concepts and Design 以及范斯滕和塔能鲍姆的 Distributed Systems 是从原理出发的教材,讨论体系结构、命名、协调、一致性、复制和容错。
克莱普曼和里科米尼的 Designing Data-Intensive Applications 讨论生产环境数据系统中的复制、分区和流处理;卡尚、盖拉维和罗德里格斯的 Introduction to Reliable and Secure Distributed Programming 是关于容错抽象与共识的研究生教材。帕切科和马伦塞克书中讲MPI的第3章是最接近的桥梁:进程间消息传递是两者的共同基础,但本书的目标是速度,而非容忍故障。当课程包含并行编程时,或者作为分布式系统教材的实践补充时,可以阅读本书。
如何使用本书
本书适合修读第一门并行计算或高性能计算课程的本科生、需要为自己的程序提速的理工科学生,以及没有并行经验的程序员。由于所有示例都用C编写,读者需要熟练掌握C语言编程,包括指针和数组。
- 核心: 先读第1–2章掌握术语和设计方法,然后至少深入学习一种接口。许多课程选讲第3章(MPI)和第5章(OpenMP);第4章(Pthreads)最清楚地展示了底层的同步机制。
- GPU选项: 第6章;如果课程以GPU为重点,可以紧接第2章阅读。
- 综合: 在学完所选接口之后读第7章;用这些接口跟着做n体问题和样本排序的解法,并比较它们的性能。
- 收尾: 第8章指出进一步学习的方向。
从作者网站下载源代码,边读边运行每个示例,并用不同数量的进程或线程计时,性能评估由此变得具体。在依赖GPU代码清单之前先查看勘误表。性能表现无法在单个核心上学会,如有条件,请使用真正的多核机器,以及小型集群或GPU。
