PMPP 第一章综述
本文是对 PMPP 第五版的总结笔记,第一章,开始是自己逐字翻译,后来发现效率实在太低,采用 DeepSeek 翻译总结,并核对关键内容。
现在很多应用都需要设备的计算能力。1980 后的二十年,cpu 发展迅速,单CPU 能带来GFLOPS 级别的浮点操作数。这使得软件发展更好,能产生更有用的结果。用户反过来也顺应了设备计算能力的提升,创造了计算机工业的正循环。
然而,2003 年后,由于能源消耗和散热设计问题。限制了CPU 的时钟频率和生产力能力在串行计算过程的表现。多核出现了。用户有多个指令序列,来自相同或者不同的应用,同时在这些处理器上运行。为了能充分利用多核,任务必须分成能在多核上同步执行的指令。在软件开发领域,多核开发影响深远。
传统程序是串行的程序运行在冯诺依曼架构上。这些程序可以通过程序计数器,让人们理解串行的步骤。程序计数器包含下一条指令地址。来自这个应用的顺序逐步执行导致指令执行的序列被称之为执行线程,简称线程。这个概念很重要,后续将会正式定义并且在书后面很多地方用到。
过去的软件开发者依赖底层的增长的时钟周期速度和多指令执行,这种高级特性开发;同样的软件,在下一代处理器上就会更快。用户也习惯了。最近,就没那么有效了。串行程序只能在一个核上运行,不再随着处理器迭代变得更明显的快了。这也导致开发者没法像新处理器一样介绍他们的新程序,减少了计算机工业的成长机会。
然而,并行软件在新一代处理器中崭露头角,多线程合作让计算更快。这种并行程序超过串行的巨大升级,称之为并行革命。实际并行程序不是啥新词。高性能计算社区已经开发并行程序数十年了。
这些并行程序运行在大规模,昂贵的计算机上。只有精英程序能使用这些计算机,导致限制了并行程序只有一小部分开发者。然而,当所有处理器变成并行计算机,大量的并行应用需要开发。需要软件开发者学习并行编程,也就是这本书。
异构并行计算
从 2003 年开始,半导体工业开始驶向两个轨道:多核和多线程。多核需要维持串行程序执行速度的同时转移到多个核。多核处理器一开始是两个核,并随着半导体工艺世代的发展增加数量。英特尔,AMD 的服务器处理器能 到 100 核以上,每个处理器都是一个可实现完整 x86 指令集的、支持超线程技术(具有两个硬件线程)的乱序多指令发射处理器,旨在最大化顺序程序的执行速度。
与多核相反,多线程路径专注于并行应用的吞吐。多线程路径开始于大量的线程,并随着每一代增加线程数。例如英伟达的 H100 有着数十万的线程,在一个简单的有序流水线执行。多线程处理器,尤其是GPU,在浮点计算处于领先地位。同样是以 H100 为例,峰值浮点吞吐能达到:
| Bits | TFLOPS |
|---|---|
| 64bit | 34 |
| 32bit | 64 |
| 16bit | 1979 |
CPU的同年的顶尖服务器处理器也只能达到个位数的TFLOPS。这不是必要的应用渠道,而是仅仅在这些芯片中,执行资源理论上能够支持的原始速度。
这两者的路径的差距已经形成一种电势累计,在未来的某一时刻,总有一个路线是走不下去。迄今为止,许多应用开发者转向了GPU 做计算密集任务。并且更为重要的,像深度学习这种本质上计算密集任务,在并行程序性能提升的情况下已经能够带来革命性的应用。计算密集的部分就是并行计算的首要目标。当有更多工作需要做的时候,就有更多的机会切分任务沿着合作的并行 worker,例如线程(threads)。

核心问题
为什么多核与多线程性能差异巨大? → 答案在于两者的基本设计哲学完全不同。
CPU:低延迟导向设计(Latency-Oriented)
| 设计特点 | 目的 |
|---|---|
| 计算单元与操作数传送逻辑大而强 | 减少单条指令/操作的延迟 |
| 大容量最后一级缓存(LLC) | 将长延迟的DRAM访问转换为短延迟的缓存访问 |
| 复杂的分支预测与执行控制逻辑 | 减轻条件分支指令带来的延迟 |
| 资源密集:消耗大量芯片面积和功耗 | 换取每个独立线程的低执行延迟 |
代价:这些资源本可用于提供更多的算术单元和内存通道。 设计风格:低延迟导向设计
GPU:吞吐导向设计(Throughput-Oriented)
驱动因素
- 电子游戏产业的快速成长
- 每帧画面需要海量浮点计算 + 海量内存访问
- 巨大的经济压力 → 推动GPU厂商优化吞吐量
关键需求
高FLOPS(每秒浮点运算次数)
高内存带宽(数据移动速度)
“正是这种数据移动,才使得游戏画面丰富且令玩家满意”
内存模型优势
- 游戏应用通常接受宽松内存模型(relaxed memory model)
- 使GPU能简单支持大量并行内存访问
- 对比:CPU必须满足传统OS/应用/IO设备的严格内存模型 → 增加内存带宽更困难
- 结果:GPU内存带宽已达同期CPU的10倍以上,且优势预计将持续
延迟 vs 吞吐的量化权衡
| 操作 | 代价(面积/功耗) |
|---|---|
| 算术吞吐量翻倍(单元数量翻倍) | 面积 ×2,功耗 ×2 |
| 算术延迟减半(电流翻倍) | 面积 >×2,功耗 ×4 |
结论:降低延迟比提高吞吐量昂贵得多。 → GPU选择优化吞吐量,允许单个线程有较长延迟,但通过大量线程隐藏延迟。
GPU设计形象比喻
| 图1.1(a) CPU | 图1.1(b) GPU |
|---|---|
| 少数大算术单元 | 大量小算术单元 |
| 少数内存通道 | 大量内存通道 |
执行模型
- 软件期望使用大规模并行线程
- 当一些线程等待长延迟操作时,硬件切换执行其他线程
- 小容量cache辅助控制带宽需求
- 设计风格:吞吐导向设计(最大化整体吞吐量,容忍单线程延迟)
CPU vs GPU:适用场景
| 场景 | 更优选择 | 原因 |
|---|---|---|
| 单线程或极少数线程 | CPU | 操作延迟低 |
| 大量线程 | GPU | 执行吞吐量高 |
| 混合型应用 | CPU + GPU | 串行部分跑CPU,计算密集型部分跑GPU |
这正是 CUDA(2007年NVIDIA推出) 的设计目标:支持CPU与GPU联合执行。
非性能因素:市场与生态
安装基数(Installed Base)
- 软件开发成本需由大量用户分摊
- 小市场占有率的处理器 → 用户基础小 → 经济上不可行
- 传统并行计算系统的问题:与通用微处理器相比,市场占有率微乎其微 → 只有少数政府/大企业资助的“精英应用”能成功开发
GPU的市场优势
- 由于PC市场的普及,GPU销量已达数亿颗
- 几乎所有台式PC和高端笔记本都包含GPU
- 至今已有超过10亿颗支持CUDA的GPU投入使用
- 巨大的市场占有率 → 对应用开发者具有经济吸引力
外形尺寸与易访问性
- 2006年以前:并行应用只能跑在数据中心服务器或部门级集群 → 限制实际应用场景
- 医学影像例子:
- 学术环境:64节点集群发论文可行
- 临床环境(如MRI机器):无法销售带成排服务器的设备 → 只能用PC + 加速器组合
- NIH曾拒绝资助并行编程项目,认为集群机器无法用于临床
- 现状:许多公司出货配备GPU的MRI产品,NIH也资助GPU计算研究
GPU编程的演变
早期(~2006年):GPGPU
- 程序员必须通过图形API(OpenGL/Direct3D)访问GPU
- 计算必须表达为绘制像素的函数
- 局限性:API限制应用类型,未能广泛普及
- 但仍激发了优秀研究成果
革命(2007年):CUDA 发布
- 不仅是软件变化,还增加了额外硬件
- NVIDIA投入芯片面积用于促进并行编程易用性
- G80及后续芯片:GPGPU程序不再经过图形接口
- 芯片上新增通用并行编程接口 → 直接服务于CUDA程序
- 极大扩展了可开发的GPU应用类型
- 软件层全部重做 → 支持熟悉的 C/C++ 编程工具
补充:其他异构计算加速器
- FPGA(现场可编程门阵列):广泛用于加速网络应用
- 本书以GPU为学习载体,但所授技术同样适用于FPGA等加速器
为什么需要更多的速度或者并行?
正如我们在第 1.1 节中所指出的,进行大规模并行编程的主要动机,是为了让应用能够在未来的硬件世代中持续获得速度提升。我们将在“并行模式”、“高级模式”以及“应用”等章节(第 2 部分和第 3 部分,即第 7 章至第 23 章)中讨论到,当一个应用适合并行执行时,其在 GPU 上的良好实现相较于在单个 CPU 核心上的顺序执行,可以实现超过 100 倍的加速。如果应用包含我们称之为“数据并行性”的特征,通常只需几个小时的工作就能实现 10 倍的加速。
尽管许多现有应用看似运行得足够快,但未来的大众市场应用将涉及过去所谓的“超级计算应用”:
- 生物学研究:向分子层面深入,需结合计算模型模拟分子活动,传统显微镜无法满足。模拟能力将受益于计算速度提升,从而扩大可建模的生物系统规模和反应时间长度。
- 视频与音频处理:从NTSC到HDTV的体验提升说明了需求的增长。未来将需要更多计算能力来实现视图合成、低分辨率视频的高分辨率显示等新功能。
- 用户界面:智能手机的高分辨率触摸屏、三维传感器与显示器、虚实结合应用、语音及视觉交互界面,都需要更高计算速度。
- 电子游戏:从预置场景转向动态物理模拟,如车轮碰撞后的真实损坏效果,需要大量计算能力来建模物理现象。“数字孪生”概念也因此兴起。
- 基于神经网络的深度学习:互联网提供了海量标注数据,GPU提供了大规模计算吞吐量,使深度学习自2012年起在计算机视觉和自然语言处理领域迅速普及。
所有这些新应用都涉及以不同方式和层次模拟/表达物理并发世界,处理海量数据,且大部分计算可以并行进行。
- 以直观的方式向非计算机专业背景的应用开发者介绍数据管理技术
- 提供实际代码示例和动手练习
- 采用 CUDA 编程模型,因为它已被大规模开发者社区验证,能有效支持并行实现和数据管理
实际应用的加速比
加速比定义
系统 A 相对于系统 B 的加速比 = B 上执行时间 / A 上执行时间
例如:若应用在 A 上需 10 秒,在 B 上需 200 秒,则 A 比 B 快 20 倍(20×)。
Amdahl 定律
并行计算可实现的加速比严重受限于应用可并行化部分的比例。
| 并行部分占比 | 并行部分速度提升 | 整体加速比 | 说明 |
|---|---|---|---|
| 30% | 100× | 约 1.42× | 即使并行部分无限加速,整体最多只能削减30%时间 |
| 99% | 100× | 约 50× | 整体执行时间降至原来的1.99% |
结论:要让大规模并行处理器有效加速,应用的绝大部分执行时间必须在可并行部分。
- 研究人员已对某些应用实现 超过 100 倍的加速
- 但这通常需要:算法增强 + 并行化 + 性能调优 三者结合
另一个影响加速比的重要因素就是 内存带宽
- 实际问题:简单的并行化往往会饱和内存(DRAM)带宽,导致加速比仅能达到 约 10 倍。
- 解决思路:需要通过多种转换利用 GPU 的专用片上内存,大幅减少对 DRAM 的访问次数。
- 进一步挑战:还需优化代码以应对片上内存容量有限等限制。
- 本书目标:帮助读者充分理解并熟练掌握这些优化技术。
CPU GPU 应合理分工
- 加速比也反映 CPU 对应用的适合程度:有些应用在 CPU 上本就表现良好,GPU 加速难度更大。
- 多数应用有部分代码更适合 CPU 执行:
- 要让 CPU 有公平发挥的机会
- 代码编写应使 GPU 与 CPU 互补
- 充分利用 CPU + GPU 的异构并行计算能力
- 现状:多核 CPU + 众核 GPU 的组合已实现:
- 桌面级:千万亿次(terascale)计算
- 集群级:百亿亿次(exascale)计算
用一个桃子来比喻的话:
| 比喻 | 对应内容 | 特点 |
|---|---|---|
| 桃肉 | 可并行的部分 | 适合 GPU 处理 |
| 桃核(pit) | 串行部分 | 极难并行化,CPU 擅长处理 |
- 重要结论:串行部分虽然可能占据大量代码量,但在“超级应用”中通常只占执行时间的一小部分。
- 早期 GPGPU 覆盖有限:只覆盖了桃肉的一小部分。
- CUDA 覆盖更广:旨在覆盖大得多的桃肉区域。

并行编程挑战
有人说过:“如果你不在乎性能,并行编程很容易。你可以在一小时内写出并行程序。但如果不在乎性能,何必还要写并行程序呢?”
本书聚焦于如何在并行编程中实现高性能,主要面临以下四大挑战:
挑战一:并行算法的计算复杂度
- 问题:许多并行算法比串行算法做了更多的工作。
- 后果:对于大数据集,并行算法可能运行得比串行还慢。
- 原因:许多实际问题天然适合用数学递推描述,并行化需要非直观的思维方式,可能引入冗余计算。
- 解决方案:
- 使用重要的算法原语,如 prefix-sum(前缀和)/ scan(扫描)
- 引入 work efficiency(工作高效性) 概念
- 详见 第11章
挑战二:内存访问限制
- 分类:
- 内存受限(memory-bound):速度受限于内存访问延迟/吞吐量
- 计算受限(compute-bound):速度受限于算术运算速率
- 问题:内存受限应用需要提高内存访问速度的方法。
- 解决方案:
- 第5、6章介绍内存访问优化技术
- 在多个并行模式和应用章节中实践这些技术
挑战三:对输入数据特性敏感
- 问题:并行程序的执行速度比串行程序更敏感到输入数据特性,如:
- 数据大小不规则
- 数据分布不均匀
- 后果:导致各线程工作负载不均,显著降低并行效率,性能可能剧烈波动。
- 解决方案:
- 数据分布正则化(regularizing)
- 根据数据特性定制数据到线程的分配
- 详见并行模式和应用相关章节
挑战四:线程协作与同步开销
| 类型 | 特点 | 同步需求 |
|---|---|---|
| 易并行(embarrassingly parallel) | 线程间几乎不需协作 | 无/极少同步 |
| 需协作的并行 | 线程间需要交互 | 需要屏障(barrier)、原子操作(atomic operation)等 |
- 问题:同步操作带来额外开销,线程经常等待其他线程而非执行有用工作。
- 解决方案:全书各处讨论减少同步开销的各种策略。
乐观的信息
大多数挑战已被研究人员解决,且不同应用领域之间存在通用模式,可以将一个领域的解决方案应用到其他领域。
这正是本书的核心方法:在重要并行计算模式和应用的背景下,介绍解决这些挑战的关键技术。
四大挑战总结表
| 挑战 | 核心问题 | 后果 | 解决方案(书中章节) |
|---|---|---|---|
| 1. 算法复杂度 | 并行算法做了更多冗余工作 | 大数据集可能比串行还慢 | work efficiency、prefix-sum(第11章) |
| 2. 内存访问 | 内存带宽/延迟成为瓶颈 | memory-bound 应用难以提速 | 内存访问优化(第5、6章) |
| 3. 数据敏感性 | 对输入大小/分布不规则敏感 | 工作负载不均,性能波动 | 数据正则化、定制分配 |
| 4. 同步开销 | 线程协作需要同步机制 | 线程等待,浪费计算资源 | 减少同步策略(全书) |
相关并行编程接口
三种并行编程模型对比总结
| 特性 | OpenMP | MPI | OpenCL / CUDA |
|---|---|---|---|
| 主要应用场景 | 共享内存多处理器系统 | 集群计算(分布式内存) | 大规模并行处理器(GPU) |
| 内存模型 | 共享内存 | 分布式内存(不共享) | GPU 内共享内存 |
| 编程方式 | 编译器指令/pragma | 显式消息传递 | 语言扩展 + API |
| 自动化程度 | 高(编译器自动化) | 低(手动域分解) | 中(显式控制) |
| 性能可移植性 | 较好 | 较高(但移植工作量大) | 需修改以优化性能 |
| 学习曲线 | 相对平缓 | 陡峭 | 中等 |
| 与 CUDA 关系 | 可用 CUDA 弥补不足 | CUDA 用于节点内,MPI 用于节点间 | CUDA 与 OpenCL 概念高度相似 |
核心要点
- OpenMP:编译器自动化程度高,适合共享内存系统;但编译器仍在演进,程序员仍需理解并行概念。
- MPI:用于分布式内存集群,适合超大规模 HPC;但节点间无共享内存,需做域分解和显式消息传递。
- OpenCL:标准化编程模型,跨厂商兼容;与 CUDA 概念高度相似,技术可相互迁移。
- CUDA 的价值:提供对并行编程细节的显式控制,是学习并行编程概念的优秀载体,即使在主要使用 OpenMP 或 OpenCL 的场景下也很有帮助。
本书目标
目标一:教授高性能并行编程
主要目标是教会读者如何为大规模并行处理器编程以实现高性能。
实现方式:
- 全书大部分篇幅 专注于 高性能并行代码开发技术
- 不过度依赖硬件专业知识,但需要概念性理解并行硬件架构
- 重点培养计算思维,使你能够以适合大规模并行处理器高性能执行的方式思考问题
关于硬件知识:
- 目前大多数处理器的高性能并行编程仍需一定的硬件知识
- 即使未来出现更好用的工具,掌握硬件知识的程序员也能更有效地使用这些工具
- 因此:
- 第4章:专门介绍 GPU 架构基础
- 在讨论高性能并行编程技术时,还会涉及更专业的架构概念
目标二:保证正确功能与可靠性
并行计算中的一个微妙问题——达到初始性能还不够,关键在于能够调试代码并支持用户。
CUDA 编程模型提供的支持:
| 功能 | 用途 |
|---|---|
| 屏障同步(barrier synchronization) | 管理并行性 |
| 原子操作(atomicity) | 保证数据一致性 |
| 内存一致性(memory consistency) | 管理内存访问顺序 |
工具支持:
- 不仅能调试功能性问题
- 还能定位性能瓶颈
目标三:跨未来硬件世代的可扩展性
确保随着未来机器越来越并行化,你的代码能在新机器上运行得更快。
关键方法:
- 规整化内存数据访问
- 局部化内存数据访问
- 减少关键资源的消耗
- 减少数据结构更新中的冲突
核心理念:
高性能并行代码的开发技术,也是确保应用程序未来可扩展性的关键。
教学方法
- 覆盖原则与模式:介绍并行编程中的若干核心原则和模式
- 结合应用场景:在并行化实际应用的过程中教授这些原则和模式
- 精选内容:选择最有用、最成熟的技术进行详细讲解
- 本版新增:大幅扩充了并行模式和应用相关章节的数量
本书组织架构
以下是这段全书组织结构的翻译总结:
全书三大部分概览
| 部分 | 内容 | 章节范围 |
|---|---|---|
| 第1部分:基础概念 | 并行编程基础、数据并行性、GPU、性能优化 | 第2–6章 |
| 第2部分:基本并行模式 | 基础并行计算模式 | 第7–15章 |
| 第3部分:高级并行模式与应用 | 更复杂的并行模式及实际应用 | 第16–23章 |
第2、3部分将应用第1部分的知识技能,并根据需要引入其他GPU架构特性和优化技术。
第1部分:基础概念(第2–6章)
| 章节 | 主题 | 核心内容 |
|---|---|---|
| 第2章 | CUDA编程模型入门 | 以向量加法为例,介绍:识别并行化代码、数据隔离与传输、内核函数编写与启动、结果回传等六步流程。基于C++扩展,支持SPMD模型。 |
| 第3章 | 并行执行模型 | 多维数据与多维线程组织;线程的创建、组织、资源绑定与数据绑定。 |
| 第4章 | GPU计算架构 | 计算核心组织、线程调度;透明可扩展性、SIMD执行与控制流分歧、多线程与延迟容忍、占用率等概念。 |
| 第5章 | GPU内存架构 | 特殊内存(用于管理数据传递、提升速度);CUDA内存分配与使用;合理使用可大幅提高数据访问吞吐量、缓解内存系统拥塞。 |
| 第6章 | 性能考量与优化策略 | 线程执行模式与内存访问模式;常用优化策略清单——将在第2、3部分反复使用。 |
第2部分:基本并行模式(第7–15章)
| 章节 | 模式 | 核心内容与教学要点 |
|---|---|---|
| 第7章 | 卷积(Convolution) | 源于数字信号处理/计算机视觉;引入常量内存和缓存。 |
| 第8章 | 模板(Stencil) | 源于求解微分方程;引入三维线程与数据组织;演示线程粒度优化。 |
| 第9章 | 直方图(Histogram) | 统计数据分析与模式识别;引入原子操作(协调并发更新)和私有化优化。 |
| 第10章 | 归约树(Reduction Tree) | 汇总输入数据集合;演示控制流分歧与过度屏障同步对性能的影响及缓解技术。 |
| 第11章 | 前缀和/扫描(Prefix Sum/Scan) | 将固有串行计算转为并行;引入工作高效性概念。 |
| 第12章 | 过滤(Filter) | 不稳定过滤(可改变顺序)与稳定过滤(保持顺序);稳定过滤是扫描模式的重要应用。 |
| 第13章 | 并行归并(Parallel Merge) | 分治工作分区策略;引入动态输入数据识别与组织。 |
| 第14章 | 排序(Sorting) | 三种并行排序算法:奇偶排序、归并排序(依赖第13章归并模式)、基数排序(依赖第11章扫描模式)。 |
| 第15章 | 矩阵乘法(Matrix Multiplication) | 深入重访,应用第6章优化策略清单中的大量高级优化技术。 |
第3部分:高级并行模式与应用(第16–23章)
本部分模式更复杂,包含更多应用上下文。重点不是引入新技术,而是应用特定的考量。
| 章节 | 主题 | 核心内容 |
|---|---|---|
| 第16章 | 动态规划与波前并行 | Floyd-Warshall算法(图处理)+ Smith-Waterman算法(基因组序列比对) |
| 第17章 | 稀疏矩阵计算 | 面向超大数据集;数据重排技术:压缩、填充、排序、转置、规整化 |
| 第18章 | 图算法 | 图搜索的高效GPU实现;多种并行化策略;图结构对算法选择的影响 |
| 第19章 | 深度学习(CNN) | 卷积神经网络的高效实现:分块技术(tiling)+ 卷积模式 |
| 第20章 | 大语言模型(LLM) | GPU上训练与推理的先进优化:KV缓存、批处理(batching) |
| 第21章 | 静电势图计算 | 散播-收集变换(scatter-to-gather)技术:增强并行性、减少同步开销 |
| 第22章 | 计算思维 | 将抽象科学问题组织为计算任务;并行算法结构及其性能影响;以输入为中心 vs 以输出为中心两种并行化策略回顾 |
| 第23章 | 异构集群CUDA编程 | 多GPU节点集群:MPI、NCCL、NVSHMEM 与 CUDA 协同使用,降低通信开销 |
第24章:结论与展望——大规模并行计算将是未来十年最激动人心的领域之一。
核心教学理念
“从具体例子中学习效果最好。”
- 全书基于 CUDA 教授并行编程
- 先掌握特定编程模型中的概念(坚实 footing)
- 再将知识泛化到其他编程模型
- 深入掌握 CUDA 能帮助你理解甚至与 CUDA 无关的并行概念
章节总览图
第1部分(基础)
第2–6章:概念 + 架构 + 优化策略清单
↓
第2部分(基本模式)
第7–15章:卷积、模板、直方图、归约、扫描、过滤、归并、排序、矩阵乘
↓
第3部分(高级模式与应用)
第16–23章:动态规划、稀疏矩阵、图算法、深度学习、LLM、静电势图、计算思维、异构集群
↓
第24章:结论与展望