多线程的核心保证
相邻流水线槽位来自不同线程,天然消除同线程内的依赖
CS152 · 第 16 课
当挖掘单线程指令级并行(ILP)的收益递减时,体系结构转向了另一个方向:与其死磕一个线程,不如让多个线程共享同一套流水线硬件。这一课从 1964 年的 CDC 6600 外围处理器一路讲到今天几乎所有高性能处理器都具备的同时多线程(SMT)。
从单一顺序指令流里持续挖掘 ILP 越来越困难;线程级并行(TLP)可以来自多道程序(多个独立的顺序作业),也可以来自显式并行化的单个应用。多线程的核心思路:把来自不同线程的指令交替送进同一条流水线,只要保证流水线中相邻的指令永远不属于同一个线程,就能保证同一线程的两条相邻指令之间不存在真正的数据/控制依赖——这样一条完全不带旁路的 5 级流水线就能正确工作,不需要任何冒险检测逻辑:"同一线程中,前一条指令的写回永远先于下一条指令的寄存器读取完成"。
硬件实现需要:按线程索引的 PC 数组、每个线程独立的寄存器堆,以及一路贯穿整条流水线、在每一级都携带的"线程选择"信号。这样的设计"在软件(包括操作系统)看来,就像是多个(尽管更慢的)独立 CPU"。代价:每个线程需要自己的用户态状态(PC、通用寄存器)和系统态状态(页表基址寄存器、异常处理寄存器);线程之间还会争抢缓存/TLB(除非把它们做得更大),并增加操作系统调度开销。
CDC 6600 外围处理器(重新验算):流水线周期 100ns,每个"虚拟"处理器每 1000ns 执行一条指令——1000ns ÷ 100ns = 10,恰好对应 10 个虚拟 I/O 处理器做 10 路固定轮转,两个数字内在自洽。这是最早的多线程硬件,选用累加器式 ISA 正是为了尽量减少每个线程需要保存的处理器状态。
Denelcor HEP(Burton Smith, 1982)是第一台在主 CPU(而非仅 I/O 处理器)中使用硬件线程的商用机器:每处理器 120 个线程,10MHz 时钟,最多 8 个处理器,是后续 Tera MTA 的前身。Tera MTA 面向大规模低局部性的超算负载,干脆不设数据缓存,完全依靠海量并行线程来隐藏访存延迟。
IBM PowerPC RS64-IV(2000):基于 PowerPC 的四发射顺序 5 级流水线,每颗物理 CPU 支持两个虚拟 CPU;一旦发生 L2 缓存缺失,就把流水线清空并切换到另一个线程执行——由于流水线短,清空代价只有 4 个周期,相对访存延迟而言微不足道;之所以选择"清空"而不是简单"停顿",是为了简化异常处理。
Oracle/Sun Niagara 系列面向数据中心 Web 服务器/数据库负载(大量并发请求):许多简单核心,每个核心配许多硬件线程,用远低于单线程性能换取每次操作更低的能耗。历代规格:Niagara-1(2004)8 核×4 线程;Niagara-2(2007)8 核×8 线程;Niagara-3(2009)16 核×8 线程;T4(2011)8 核×8 线程;T5(2012)16 核×8 线程;M5(2012)6 核×8 线程;M6(2013)12 核×8 线程——幻灯片明确指出 Oracle 在 M8(2017)之后终止了 SPARC 系列。
前面的多线程技术都是"垂直"的——每个流水级在任意时刻只处理一个线程。同时多线程(SMT)不同:它利用乱序超标量处理器本来就有的精细调度能力,让同一个周期里来自多个线程的指令进入不同的执行槽位。引用 Tullsen, Eggers, Levy, "Simultaneous Multithreading: Maximizing On-chip Parallelism," ISCA 1995——多数应用在乱序超标量里,大部分执行单元大部分时间是空闲的。
课程用两种"浪费"来划分空间:垂直浪费——整个周期完全没有任何指令发射(完全空闲);水平浪费——一个周期里发射的指令数少于机器的发射宽度(部分空闲)。垂直多线程能消除垂直浪费(不再有完全空闲的周期),但留下水平浪费不变;芯片多处理(CMP)把宽发射拆成多个较窄的独立处理器,让每个处理器更容易被填满(减少水平浪费),但也给单个线程的峰值吞吐率设了一个上限(该线程被限制在其中一个较窄的处理器里);理想的超标量多线程则完全不限制哪个线程可以使用哪个槽位、哪个周期——理论上同时消除垂直和水平浪费。
真正的 SMT 实现(引用 Tullsen, Eggers, Emer, Levy, Stamm, Lo, DEC/UW, 1996):增加多套硬件线程上下文和多个取指引擎,让不同线程取出的指令能够利用乱序发射队列本就具备的多源调度电路同时发射;关键特性是当只有一个线程在跑时,它依然可以独占整台机器——SMT 会根据线程级并行度自动调整:TLP 高时,整机宽度被所有线程共享;TLP 低时,整机宽度都留给单一线程去挖掘 ILP。
真实商用 SMT:Intel Pentium 4 超线程(2002)——第一款商用 SMT 设计,2 路 SMT,逻辑处理器共享几乎所有物理资源(缓存、执行单元、分支预测器),硬件面积开销约 5%;设计上保证任何共享队列都不会被单个逻辑处理器 100% 占满(防止饥饿);单线程运行时性能与关闭超线程时基本相当。历史上,超线程在基于 P6 的乱序后续产品(Pentium-M、Core Duo、Core 2 Duo)上曾被取消,直到 2008 年 Nehalem 世代才恢复;第一代 Intel Atom(顺序执行的 x86 核)则采用了两路垂直多线程而非 SMT——由此得到一个简洁的等价关系:"超线程" = (Intel 乱序核上的 SMT)或(Intel 顺序核上的垂直多线程)。
IBM Power 5(2004)相对单线程的 Power 4(2001)新增了 2 路 SMT 所需的结构:两套取指(PC)、两套初始译码、两套提交(架构寄存器集)——中间共享的乱序执行核心基本保持不变。Power 5 的数据通路图明确标注哪些结构是"两线程共享"(分支预测、分支历史表、返回栈、目标缓存、共享发射队列、共享寄存器映射器、共享执行单元、共享数据缓存/L2)、哪些是"线程 0 专用"/"线程 1 专用"(各自的指令缓冲)。幻灯片直接回答了"为什么只做 2 路而不是 4 路":4 路会让共享资源(物理寄存器、缓存、内存带宽)中的某一个更容易成为瓶颈。
真实测得的 SMT 加速比:Pentium 4 Extreme,SPECint_rate 提速 1.01×,SPECfp_rate 提速 1.07×;把 26 个 SPEC 基准两两配对运行(幻灯片写作"26² 次运行",即 676 次),加速比范围 0.90~1.58,平均 1.20;Power 5 八处理器服务器,SPECint_rate 提速 1.23×,SPECfp_rate 提速 1.16×;同一应用跑两份拷贝时,加速比范围 0.89~1.41,浮点应用因为缓存冲突更严重,收益普遍最小。(引用 Bulpin et al. 的研究)不同程序两两搭配运行时相互影响差异巨大:例如 181.mcf 的加速比几乎不受搭档影响(除非搭档同样大量占用 L2),而 172.mgrid 对搭档非常敏感。
SMT 与安全:多数硬件旁路攻击都依赖一个被多个执行上下文共享的硬件资源来建立隐蔽信道——SMT 恰好让攻击者线程对与其同时运行的受害者线程共享的资源拥有高带宽访问能力。三个真实的命名漏洞:TLBleed(利用 TLB,2018 年 6 月)、CacheBleed(利用 L1 缓存,2016 年)、PortSmash(利用功能单元端口,2018 年 11 月)。真实世界的应对:OpenBSD 6.4 默认在 BIOS 中关闭了超线程。
问题 1:为什么把不同线程的指令交替送入同一条流水线,可以让一条完全不带旁路的流水线正确工作?
问题 2:"垂直多线程"和"同时多线程(SMT)"的关键区别是什么?
问题 3:为什么 SMT(同时多线程)会给硬件旁路攻击(如 TLBleed、CacheBleed、PortSmash)提供便利?
相邻流水线槽位来自不同线程,天然消除同线程内的依赖
固定轮转、软件控制轮转、硬件控制调度,各配真实历史机器
垂直多线程/CMP 各消除一种,SMT 同时消除两种
提升资源利用率的同时,也打开了新的硬件旁路攻击面
sp23/lecture_slides/L16_Multithreading.pdf(UC Berkeley CS152/CS252A, Spring 2023, Sophia Shao)