跳转到主要内容

3.9 利用多发射与静态调度开发 ILP

前面各节介绍了多发射处理器利用硬件开发 ILP 的技术。尽管编译程序时假设指令顺序执行,一次一条、互不重叠,硬件仍利用分支预测、寄存器重命名和动态调度来发现并开发 ILP。相比之下,VLIW 处理器每次发射固定数量的指令,它们被组织成一条大指令,或一个固定的指令包,并在指令中明确标出并行性。VLIW 天生由编译器静态调度。Intel 和 HP 创建附录 H 所述的 IA-64 体系结构时,还为这种体系结构风格引入了 EPIC 名称,即 explicitly parallel instruction computer,显式并行指令计算机。

基本 VLIW 方法

VLIW 使用多个独立功能单元。它不试图分别向这些单元发射多条独立指令,而是把多个操作打包成一条很长的指令,或者要求发射包内的指令满足相同约束。两种方式没有本质区别,因此下面沿用最初的 VLIW 方法,假设多个操作放在同一条指令中。

VLIW 的优势随最大发射率增加而扩大,因此我们重点考察更宽的发射。事实上,对于简单双发射处理器,超标量方式的额外开销可能很小。许多设计者也会认为四发射处理器的开销仍可控制,但如本章前面所示,开销增长是限制进一步加宽发射的主要因素。

考虑一个 VLIW 处理器,每条指令包含五个操作:一个整数操作,也可以是分支;两个浮点操作;两次内存访问。指令为每个功能单元保留一组字段,每个单元可能需要 16~24 位,总指令长度为 80~120 位。相比之下,Intel Itanium 1 和 2 每个指令包包含六个操作,即可以同时发射两个各含三条指令的指令束,详见附录 H。

为了使功能单元保持忙碌,代码序列必须有足够并行性,填满可用操作槽。通过循环展开,并在扩大的单个循环体内调度代码,就能发现这种并行性。如果展开产生直线代码,可以采用只处理单个基本块的局部调度技术。如果要跨越分支调度,才能发现并开发并行性,就必须使用复杂得多的全局调度算法。全局调度不仅结构更复杂,还要处理复杂得多的优化取舍,因为跨分支移动代码代价很高。

附录 H 将介绍踪迹调度(trace scheduling),这是一种专为 VLIW 开发的全局调度技术;还会介绍特殊硬件支持,用来消除部分条件分支,扩展局部调度的适用范围,并提高全局调度的性能。

目前,我们先依靠循环展开,生成较长的直线代码,再用局部调度组成 VLIW 指令,重点考察这类处理器的执行效果。

例题 假设一个 VLIW 每周期可发射两次内存访问、两个浮点操作,以及一个整数操作或分支。为它给出循环 x[i]=x[i]+sx[i]=x[i]+s 的展开版本,RISC-V 代码见 3.2 节。充分展开,以消除所有停顿。1

解答 图 3.28 给出代码。循环展开为七份循环体,消除了所有停顿,即完全空闲的发射周期。展开并调度后的循环需要 9 个周期,因此每 9 周期产生 7 个结果,平均每个结果 1.29 周期,几乎是 3.2 节采用展开、调度代码的双发射超标量处理器的两倍速度。1

原始 VLIW 模型在技术和实际使用层面都遇到问题,影响了效率。技术问题主要是代码增大,以及同步锁步运行的限制。两个因素共同导致 VLIW 代码显著增大。第一,要在直线代码片段中产生足够操作,就需要像前面的例子那样大量展开循环,从而扩大代码。第二,只要指令没填满,未使用功能单元对应的编码位就被浪费。附录 H 将讨论软件流水线等软件调度方法,在不显著扩大代码的情况下,获得循环展开的收益。

为抑制代码增大,有时会采用巧妙编码。例如,只设置一个较大的立即数字段,供任意功能单元使用。另一种方法是在主存中压缩指令,读入缓存或译码时再展开。附录 H将介绍其他技术,并记录 IA-64 中明显的代码膨胀。

内存访问 1内存访问 2浮点操作 1浮点操作 2整数操作/分支
fld f0,0(x1)fld f6,-8(x1)
fld f10,-16(x1)fld f14,-24(x1)
fld f18,-32(x1)fld f22,-40(x1)fadd.d f4,f0,f2fadd.d f8,f6,f2
fld f26,-48(x1)fadd.d f12,f0,f2fadd.d f16,f14,f2
fadd.d f20,f18,f2fadd.d f24,f22,f2
fsd f4,0(x1)fsd f8,-8(x1)fadd.d f28,f26,f24
fsd f12,-16(x1)fsd f16,-24(x1)addi x1,x1,-56
fsd f20,24(x1)fsd f24,16(x1)
fsd f28,8(x1)bne x1,x2,Loop
图 3.28 替代展开循环序列的 VLIW 指令

图 3.28 构成内层循环、替代原展开序列的 VLIW 指令。假设分支预测正确,该代码需要 9 个周期。9 周期发射 23 个操作,即每周期 2.5 个。效率,即包含实际操作的可用槽位比例,约为 60%。2 要达到这一发射率,需要比 RISC-V 通常在该循环中使用的更多寄存器。上述 VLIW 代码至少需要 8 个浮点寄存器;相应的基础 RISC-V 代码最少可用 2 个浮点寄存器,展开、调度后可能需要 5 个。

早期 VLIW 以锁步方式运行,完全没有冒险检测硬件。这种结构要求任意功能单元流水线停顿时,整个处理器都必须停顿,因为所有功能单元必须同步。编译器也许能对延迟确定的功能单元进行调度,以避免停顿,但很难预测哪些数据访问会因缓存而停顿,并据此调度。因此,缓存必须是阻塞式的,一旦阻塞就使所有功能单元停顿。随着发射率与内存访问数量增加,这种同步限制变得不可接受。较新的处理器让功能单元更加独立:由编译器避免发射时的冒险,而指令发射后,通过硬件检查允许不同步执行。

对于通用 VLIW 或需要运行第三方软件的 VLIW,二进制代码兼容性也是重大实际问题。在严格 VLIW 方案中,代码序列不仅依赖指令集定义,还依赖具体流水线结构,包括功能单元和延迟。因此,功能单元数量与延迟不同,就需要不同代码版本。这使跨代实现,或不同发射宽度实现之间的迁移,比超标量设计更困难。当然,要充分利用新超标量设计的性能,也可能需要重新编译;不过,能够直接运行旧二进制文件,仍是超标量方案的一项实际优势。在第 7 章介绍的领域专用体系结构中,这个问题不严重,因为应用就是针对特定体系结构配置编写的。

以 IA-64 为主要代表的 EPIC,为早期通用 VLIW 设计的许多问题提供了解决办法,包括支持更激进软件推测的扩展,以及在保留二进制兼容性的同时,克服代码依赖具体硬件的限制。

多发射的整体认识

图 3.29 总结多发射的基本方法、区别性特征,以及采用各方案的处理器。静态调度超标量处理器每周期发射的指令数虽可变,而非固定,但在概念上其实更接近 VLIW,因为两者都依靠编译器为处理器调度代码。随着发射宽度增加,静态调度超标量的优势递减,因此主要用于较窄发射,通常仅为双发射。若干低功耗处理器,例如本章后面介绍的 Arm A53,就是双发射、静态调度超标量处理器。超过这个宽度,大多数设计者会选择 VLIW,或动态调度并支持推测的超标量方案。

EPIC 体系结构在 2020 年取消之后,动态、推测超标量处理器成为服务器、桌面、笔记本乃至手机系统的主流。因此,“超标量处理器”通常就是指动态调度、支持推测执行的设计。有些芯片采用称为 big.LITTLE 的组合,同时包含动态调度与静态调度超标量处理器,交由软件根据使用场景,选择哪种处理器以优化性能或降低功耗。VLIW 在信号处理应用中仍然流行;第 7 章讨论领域专用加速器时,还会再次遇到它们。

所有多发射处理器的主要挑战,都是开发大量 ILP。如果并行性来自对浮点程序中简单循环的展开,那么原循环很可能也能在下一章介绍的向量处理器上高效运行。对这类应用,多发射处理器是否优于向量处理器并不明确:成本相近,而向量处理器通常同样快,甚至更快。多发射处理器的潜在优势,是能从结构较不规则的代码中提取一定并行性,并容易对各种形式的数据使用缓存。因此,多发射已成为开发 ILP 的主要方法,向量则主要成为这类处理器的扩展。

通用名称发射组织冒险检测调度区别性特征示例
静态超标量动态硬件静态顺序执行主要在嵌入式领域:MIPS、ARM,包括 Cortex-A53
动态超标量动态硬件动态部分乱序执行,但不推测目前没有
超标量(动态并推测)动态硬件带推测的动态调度带推测的乱序执行Intel Xeon 和 Core、AMD EPYC 和 Ryzen、ARM Neoverse 及许多 Cortex-A、Apple M1
VLIW/LIW静态主要依靠软件静态所有冒险由编译器确定并指示,通常为隐式指示多见于信号处理,如 TI C6x
EPIC主要为静态主要依靠软件主要为静态所有冒险由编译器确定并显式指示Intel Itanium
图 3.29 多发射处理器的五种主要方法

图 3.29 多发射处理器使用的五种主要方法,以及区分它们的主要特征。本章侧重依赖硬件的技术,它们都是某种形式的超标量;附录 H 侧重编译器方法。IA-64 所体现的 EPIC 扩展了早期 VLIW 的许多概念,融合了静态与动态方法。


  1. 译注:解答称“3.2 节的双发射超标量”,而 3.2 节所列展开调度按每周期一条指令计时。 ↩︎ ↩︎

  2. 译注:原表第 4 行写 fadd.d f12,f0,f2,与第 3 个元素加载到 f10 不符;第 6 行写 fadd.d f28,f26,f24,加数不是其他元素使用的标量 f2。原图注效率为“约 60%”,但按所示 9 周期、每周期 5 槽、23 个操作计算,应为 23/(9×5)≈51.1%23/(9\times5)\approx51.1\%。9/7≈1.299/7\approx1.29 周期/结果的算术本身成立。 ↩︎

表格阅读