本页目录

并行 I · 并行模型与缓存一致性

对标:CMU 15-418 / Berkeley CS267 | 前置:perf-01(Amdahl)、os-02(并发)、csapp-02(缓存) perf 线在单核上榨性能,到了极限就得用多核。但并行不是"加核就快"——它有自己的定律(Amdahl/Gustafson)、自己的硬件现实(缓存一致性)、自己的编程模型。这一页建立并行的世界观:并行的加速上限在哪、多核共享内存底下的缓存一致性怎么运作、以及数据并行 vs 任务并行的划分。这是走向 GPU(gpu 线)和 MLSys(mlsys 线)的地基。

1. 并行的加速上限:Amdahl vs Gustafson

Amdahl(固定问题,悲观) vs Gustafson(问题随核扩,乐观) 对比。

图 par-01.3Amdahl(固定问题,悲观) vs Gustafson(问题随核扩,乐观) 对比。

Amdahl 定律(perf-01 的并行版):程序中串行部分占比 \(s\),用 \(N\) 核,加速比 \(\le \frac{1}{s + (1-s)/N}\)残酷推论:只要有 5% 串行部分,无论多少核,加速比不超过 20×串行瓶颈是并行的天花板——这解释了为什么"加核收益递减"。

Gustafson 定律(乐观的另一面):实践中问题规模常随核数增长(更多核就处理更大数据)——此时可并行部分随规模扩大、串行占比相对缩小,加速接近线性。"固定问题看 Amdahl(悲观),扩展问题看 Gustafson(乐观)"——两者不矛盾,是看问题规模是否固定。ML 训练就是 Gustafson 的胜利:更多 GPU → 训更大模型/更大 batch。

关键认知并行前先问"串行部分是什么、能否消除"——同步点、共享资源争用(os-02 锁)、不可并行的数据依赖,都是串行瓶颈。减少串行比增加核数更重要

2. 缓存一致性:多核共享内存的底层魔法

MESI 缓存一致性状态机 + 伪共享(两核改同一缓存行乒乓)。

图 par-01.2MESI 缓存一致性状态机 + 伪共享(两核改同一缓存行乒乓)。

多核各有自己的 L1/L2 缓存(csapp-02),却共享同一主存——如果核 A 改了变量 x(在它的缓存里),核 B 缓存里的旧 x 怎么办? 这就是缓存一致性(cache coherence)问题,由硬件协议(MESI)解决:

读法共享内存并行的"共享"是有硬件成本的幻觉——理解 MESI,你就懂了"为什么线程私有数据要对齐到缓存行""为什么争用同一个原子计数器不 scale"。多核性能的很多坑都在这条缓存一致性总线上

3. 并行的两种分解

数据并行 vs 任务并行 的分解方式。

图 par-01.1数据并行 vs 任务并行 的分解方式。

把工作拆给多核,两种基本模式:

分治天然并行(🔗 algo-01):归并排序左右两半可并行、MapReduce(dist-03)是数据并行的分布式版。识别"哪部分是数据并行"是并行化的第一步——数据并行好扩展,优先找它。

4. 共享内存并行编程模型

在多核上写并行代码的几种抽象:

选择:规整数据并行用 OpenMP/GPU、不规则任务用工作窃取、跨机器用 MPI。"共享内存 vs 消息传递"是并行编程的两大哲学——前者方便但受限于单机 + 一致性成本,后者可扩展到千万核但要显式通信。

5. 练习与要点

例 1(Amdahl 算天花板) 程序 90% 可并行,无限核的加速上限?(\(1/0.1 = 10×\))——"10% 串行就锁死在 10× 以内",理解为什么要死磕串行部分

例 2(伪共享复现) 4 线程各累加一个数组的相邻 4 元素(同缓存行)vs 各隔 64 字节——后者快数倍。用 perf 看缓存一致性流量。MESI 的代价亲手可测(也是 os-02 例 3 的硬件解释)。

例 3(找数据并行) 给"图像每像素做 gamma 校正""快排""网页服务器处理请求"分类到数据并行/任务并行——培养"这活儿怎么拆给多核"的直觉,并行化的第一判断。\(\blacksquare\)


下一页:并行 II——同步原语与无锁结构:原子操作、内存序,以及不用锁也能正确并发的深水区。