跳转至

SIMD、多核与缓存一致性

为什么需要多种并行

单条指令内部、连续数据元素之间、独立线程之间都有潜在并行。不同层次需要不同硬件:

  • 指令级并行让多条无关指令重叠;
  • SIMD/向量让一条指令处理多个数据元素;
  • 多核让多个线程真正同时执行;
  • 多机并行再把通信扩展到网络。

并行不是复制运算单元就结束,还必须供给数据、划分任务并协调共享状态。

SIMD 与向量计算

标量加法一次处理一对数,SIMD 指令可同时处理若干 lane:

[a0 a1 a2 a3] + [b0 b1 b2 b3]
[c0 c1 c2 c3]

理想情况下,规则数组运算能以较少指令获得高吞吐。限制包括:

  • 数据长度与对齐不合适,需要处理尾部;
  • 条件分支造成 lane 掩码和利用率下降;
  • gather/scatter 不连续访存昂贵;
  • 内存带宽可能先于算力饱和。

向量 ISA 让向量长度更抽象,硬件可按实际 lane 数分批执行,软件兼容性更好。

多核的共享内存幻觉

每个核心通常有私有 L1/L2 cache,却共享更低层 cache 或主存。若核心 A 修改变量 x,核心 B 的私有 cache 可能仍保存旧副本。没有协议,“共享内存”就不能维持单一值的直觉。

cache coherence

一致性协议保证对同一 cache 行的写最终按规则被其他核心观察。常见写失效协议在一个核心获得可写权限时,使其他核心副本失效。

MESI 用四类状态描述 cache 行:

  • Modified:本核独占且已修改,下层可能旧;
  • Exclusive:本核独占但干净;
  • Shared:可能有多个干净副本;
  • Invalid:本核副本不可用。

核心写 Shared 行前要请求所有权并让其他副本转为 Invalid。总线嗅探适合核心数较少的共享互连;目录协议记录共享者,扩展性更好但需要目录存储和消息流程。

false sharing

两个线程修改不同变量,若变量恰在同一 cache 行,一致性仍以整行为单位反复转移所有权:

cache line: | counter_A | counter_B | padding... |
core A 写 A,core B 写 B,却互相失效

程序语义没有共享同一变量,性能上却像激烈竞争。通过填充、分片或改变数据布局可缓解,但会增加内存占用。

coherence 不等于 consistency

coherence 主要回答同一地址的写如何传播;memory consistency 回答不同地址的 load/store 在其他核心看来允许怎样排序。

处理器和编译器会重排独立访存以提高性能。同步算法需要原子指令与内存屏障建立顺序。例如“先写数据,再发布 ready 标志”必须保证另一个核心看到 ready 后也能看到数据。

更强顺序模型易编程,却限制优化;较弱模型允许更高性能,但软件必须明确同步。语言内存模型、编译器和 ISA 三层都要一致配合。

原子操作

原子读改写保证观察和更新不可被其他核心插入,常见有 compare-and-swap、fetch-add 或 load-reserved/store-conditional。它们是锁和无锁结构的基础,但高竞争原子变量会让 cache 行频繁迁移,成为串行瓶颈。

最小并行例子

把长度为 \(N\) 的数组求和分给 \(P\) 个核心。每核计算局部和,最后归约。理想计算量约降为 \(N/P\),但还需分配任务、读取内存、同步和合并结果。若每核都频繁更新同一个全局和,原子竞争会抵消并行收益;局部累加后一次合并通常更好。

历史与改进空间

从提高频率转向多核后,一致性流量和内存带宽成为关键约束。更多核心不只增加算力,也扩大通信距离和协议状态。分层目录、非一致内存访问(NUMA)、消息传递和领域专用互连都是不同折中。

自测

  1. 什么工作负载适合 SIMD,什么控制模式会降低 lane 利用率?
  2. false sharing 为什么不影响程序逻辑正确性,却能严重拖慢性能?
  3. coherence 与 consistency 的问题边界分别是什么?