ESC
输入关键词搜索文章
目录

现代处理器:DLP 与 TLP

嵌入式智能系统与新型计算架构 · L03b
向量化指令能让同一段代码快多少倍?多线程编程有哪些陷阱?
CH01 · 数据级并行
SIMD:一条指令,多份数据

如果说 ILP 是在时间维度上重叠指令执行,那么 DLP(Data-Level Parallelism)就是在空间维度上同时处理多份数据。SIMD(Single Instruction, Multiple Data)是 DLP 的核心实现方式。

想象你有一堆相同的加法要做:给数组中每个元素加 1。标量处理器需要逐个执行:

for i in range(n):
    a[i] = a[i] + 1   # 一次只算一个

SIMD 处理器可以一次性对 4 个、8 个甚至 16 个元素同时做加法:

// AVX-512: 512bit 寄存器 = 16 x 32bit float
__m512 vec = _mm512_load_ps(a);
vec = _mm512_add_ps(vec, one_vec);  // 一次加 16 个
_mm512_store_ps(a, vec);
为什么 SIMD 能快?

标量代码需要 n 次循环迭代、n 次分支判断、n 次地址计算。SIMD 代码把循环展开和指令融合打包在硬件层面完成,控制开销被摊薄到接近零。

主流 SIMD 指令集演进:

指令集位宽同时操作数代表平台
SSE128 bit4 x floatIntel x86 (1999)
AVX256 bit8 x floatIntel Sandy Bridge (2011)
AVX-512512 bit16 x floatIntel Skylake-X (2017)
NEON128 bit4 x floatARM (2005)
SVE/SVE2可变 128-2048 bit灵活ARMv8-A+ (2016)

一个经典案例是课件中提到的 sin(x) 泰勒展开:$\sin(x) = x - \frac{x^3}{3!} + \frac{x^5}{5!} - \frac{x^7}{7!} + \cdots$。用 SIMD 向量化后,可以同时计算多个 x 值的 sin 值,理论加速比等于 SIMD 宽度(4x-16x)。实际中由于内存带宽和加载/存储开销,通常能达到 3x-8x。

CH02 · 线程级并行
多核处理器:从单核到百核

当 ILP 和 DLP 都挖完之后,处理器设计师把目光转向了更高层次的并行——线程级并行(TLP)。既然一个核心跑不快,那就多放几个核心。

多核处理器的演进轨迹:

  • 2005:Intel 双核 Pentium D,"胶水"多核
  • 2006:Intel Core 2 Duo,原生双核
  • 2008:Intel Core i7,四核 + 超线程
  • 2017:AMD Ryzen Threadripper,16 核
  • 2023:AMD EPYC Genoa,96 核
  • 2024:Apple M3 Ultra,32 核
多核不是免费的午餐

多核处理器面临的核心挑战是缓存一致性(Cache Coherence)。当多个核心同时读写同一块内存时,它们的私有缓存(L1/L2)中的数据副本可能不一致。需要硬件协议来保证「任何时刻,所有核心看到同一地址的数据是一致的」。

MESI 协议是最经典的缓存一致性协议,定义了每个缓存行的四种状态:

  • Modified:已修改,与主存不一致,独占总线
  • Exclusive:独占,与主存一致,只有一个缓存拥有
  • Shared:共享,与主存一致,多个缓存同时拥有
  • Invalid:无效,数据已过时

当核心 A 写入一个 Shared 状态的缓存行时,需要向总线广播 Invalidate 消息,让其他核心作废自己的副本。这个广播操作就是缓存一致性流量,是多核扩展的主要瓶颈之一。

CH03 · 超线程
SMT:一个核心,两个面孔

多核需要复制整个核心(ALU、寄存器堆、缓存等),面积开销大。超线程(Simultaneous Multi-Threading, SMT)是一种更经济的方案:只复制指令调度和寄存器状态,共享执行单元。

想象一个核心有两套「前台」(程序计数器、寄存器、重命名表),但只有一个「后台」(ALU、Load/Store 单元)。当线程 A 在等待缓存未命中时,线程 B 可以占用空闲的执行单元继续干活。

超线程的收益与局限

Intel 超线程通常带来 15-30% 的性能提升,代价只有约 5% 的核心面积增加。但如果两个线程同时竞争同一类资源(如浮点单元),性能反而可能下降。

现代处理器的 SMT 宽度:Intel x86 通常 2-way SMT(一个核心 2 线程),IBM POWER 可达 8-way SMT。

一个需要注意的陷阱是伪共享(False Sharing):两个线程修改不同变量,但这两个变量恰好落在同一个缓存行(通常 64 字节)。虽然逻辑上没有数据竞争,但缓存一致性协议会在两个核心之间不断来回无效化该缓存行,导致性能暴跌。

CH04 · DLP vs TLP 的权衡
什么时候用向量,什么时候用线程?

三种并行层次不是互斥的,而是互补的。一个优化良好的程序通常会同时利用所有层次:

并行类型粒度谁负责发现适用场景典型加速比
ILP指令级硬件动态调度通用计算2-4x
DLP (SIMD)数据级编译器/程序员向量运算、图像处理4-16x
TLP (多核)线程级程序员/运行时独立任务、服务请求核心数 x

实际编程中的选择策略:

  • 循环体内有大量相同操作 → 先尝试 SIMD 向量化
  • 循环之间存在数据依赖 → SIMD 受限,考虑多线程分块
  • 任务之间完全独立 → 多线程/多进程并行
  • 既有数据并行又有任务并行 → 混合:多线程外层 + SIMD 内层
CH05 · 课后思考
思考与延伸
  1. ARM SVE 的可变向量长度设计有什么优势?为什么比固定宽度的 AVX-512 更适合未来处理器?
  2. 在 64 核心服务器上运行单线程程序,为什么性能可能不如在 8 核心上?(提示:NUMA、缓存拓扑)
  3. 编写一个会触发 False Sharing 的 C 程序,并用 perf c2c 检测缓存行竞争。如何修复?
  4. Apple M 芯片的「性能核 + 能效核」设计,在 TLP 调度上有什么独特挑战?操作系统如何决定线程放在哪种核心上?