跳转到主要内容

H.6 Intel IA-64 体系结构与 Itanium 处理器

本节概述 Intel IA-64 体系结构,以及它在 Itanium 处理器中的实现。原书将其视为最先进的 VLIW 风格处理器。

Intel IA-64 指令集体系结构

IA-64 是 RISC 风格的寄存器—寄存器指令集,但包含许多新颖特性,支持编译器开发 ILP。这里着重介绍 IA-64 ISA 的独特之处;其中多数已经在本附录讨论过,包括谓词化、由编译器检测并行性,以及存储器访问推测。

发布 IA-64 时,HP 和 Intel 引入 EPIC(Explicitly Parallel Instruction Computer,显式并行指令计算机)一词,以区别于早期 VLIW 和其他 RISC 体系结构。VLIW 与 EPIC 共享许多特性,但 EPIC 还加入了若干扩展,主要分为两类:

  1. 更灵活地表示指令间并行性,并提供更灵活的指令格式。 VLIW 依赖固定格式,要求一条宽指令内的所有操作都能并行执行,格式也非常僵硬。EPIC 则显式标示可能存在的指令依赖,并采用多种指令格式。与僵硬的 VLIW 方法相比,EPIC 能更灵活地表达并行性,也能减少不灵活指令格式通常带来的代码膨胀。
  2. 提供更全面的软件推测支持。 早期 VLIW 方案在这方面的支持很有限。

此外,IA-64 还包含寄存器窗口、可轮转的浮点寄存器(FPR)栈等多种提升性能的特性。

IA-64 寄存器模型

IA-64 的寄存器状态包括:

  • 128 个 64 位通用寄存器;稍后会看到,它们实际为 65 位宽。
  • 128 个 82 位浮点寄存器,比标准 80 位 IEEE 格式多两位指数。
  • 64 个 1 位谓词寄存器。
  • 8 个用于间接分支的 64 位分支寄存器。
  • 用于系统控制、存储映射、性能计数器,以及与操作系统通信的其他寄存器。

整数寄存器采用寄存器栈机制来加速过程调用,类似 Berkeley RISC-I 中提出、后来用于 SPARC 的机制。0–31 号寄存器始终可访问,并以 0–31 编址;32–128 号寄存器用作寄存器栈,每个过程分配 0–96 个寄存器。1 硬件通过寄存器重命名,为被调用过程创建新的栈帧;一个称为当前帧指针(CFM)的特殊寄存器,指向该过程使用的寄存器集合。栈帧分为局部区和输出区:局部区存放局部数据,输出区向被调用过程传递值。alloc 指令指定这两个区域的大小。只有整数寄存器支持寄存器栈。

发生过程调用时,更新 CFM,使被调用过程的 R32 指向输出区的第一个寄存器。原文在此将输出区也称为“被调用过程”的输出区;随后说明,这样就能把调用者的参数传入被调用者可寻址的寄存器。2 被调用者执行 alloc,分配所需的局部寄存器和用于进一步调用其他过程、传递参数的输出寄存器。

局部寄存器包含调用者的输出寄存器。专用加载和存储指令负责保存及恢复寄存器栈;称为寄存器栈引擎的专用硬件处理栈溢出。

除整数寄存器外,还有浮点、谓词和分支寄存器。浮点寄存器保存浮点数据,分支寄存器保存间接分支的目标地址。谓词寄存器保存控制谓词指令执行的谓词;稍后详述这一机制。

整数和浮点寄存器都为 32–128 号寄存器支持轮转。1 寄存器轮转旨在简化软件流水线循环的寄存器分配,这是 H.3 节讨论过的难题。结合谓词化,还可以避免循环展开,以及独立的序言、尾声代码。这能减少软件流水线引起的代码膨胀,并使它适用于迭代次数较少的循环;在传统方法中,这类循环的额外开销往往抵消许多收益。

指令格式与显式并行支持

IA-64 的设计目标,是获得 VLIW 方法的主要优势——一条指令内操作的隐式并行性,以及操作字段的固定格式——同时保有比通常 VLIW 更大的灵活性。它依赖编译器检测 ILP,把指令调度到并行指令槽;同时增加格式灵活性,并允许编译器标明一条指令不能与其后继指令并行执行。

IA-64 用两个不同概念,兼顾隐式并行性与易于译码。把指令放入指令组(instruction group)来表达隐式并行;用包含三条指令的 bundle 实现多条指令的固定打包格式。先定义指令组。

指令组是一段连续指令序列,其中没有寄存器数据依赖,只有少数例外。只要硬件资源足够,而且保持经由存储器产生的依赖,组内所有指令都可以并行执行。组的长度没有固定限制,但编译器必须显式标出组间边界,即在属于不同组的两条指令之间插入 stop 标记。要解释 stop 如何表示,必须先说明指令怎样放进 bundle。

IA-64 将指令编码为 128 位 bundle。每个 bundle 包含一个 5 位模板字段和三条各 41 位的指令。严格说,这些 41 位单元不能单独算作指令,必须结合模板字段才能解释;有时称之为 syllable。为简便,这里仍称“指令”。为简化译码与发射,模板字段规定 bundle 中每条指令所需的执行单元类型。图 H.6 给出五种槽类型、可容纳的指令类别和例子。

执行单元槽指令类型指令说明指令示例
I-unitA整数 ALU加、减、AND、OR、比较
I-unitI非 ALU 整数操作整数和多媒体移位、位测试、传送
M-unitA整数 ALU加、减、AND、OR、比较
M-unitM存储器访问整数/浮点寄存器的加载和存储
F-unitF浮点浮点指令
B-unitB分支条件分支、调用、循环分支
L + XL + X扩展扩展立即数、stop 和空操作
图 H.6 IA-64 的五种执行单元槽及其可容纳的指令类型。A 型对应整数 ALU 指令,可以放入 I-unit 或 M-unit 槽。L + X 比较特殊,占用两个指令槽,用于编码 64 位立即数和少数特殊指令,由 I-unit 或 B-unit 执行。

每个 bundle 的 5 位模板字段,同时描述与该 bundle 关联的 stop,以及其中各条指令需要的执行单元类型。图 H.7 列出模板能编码的格式和 stop 位置。它只能表示所有可能的指令类型与 stop 组合中的一个子集。下面用例子说明。

例题 将数组递增循环 x[i] = x[i] + s 展开七次,并把指令放入 bundle。先忽略流水线延迟,以最小化 bundle 数量;再调度代码,以最小化停顿。假设每周期执行一个 bundle,任何停顿都使整个 bundle 停顿;采用图 3.2 的流水线延迟。为简便,使用 MIPS 助记符。

模板值槽 0槽 1槽 2stop 位置
0MII无
1MII槽 2 后
2MII槽 1 后
3MII槽 1 后、槽 2 后
4MLX无
5MLX槽 2 后
8MMI无
9MMI槽 2 后
10MMI槽 0 后
11MMI槽 0 后、槽 2 后
12MFI无
13MFI槽 2 后
14MMF无
15MMF槽 2 后
16MIB无
17MIB槽 2 后
18MBB无
19MBB槽 2 后
22BBB无
23BBB槽 2 后
24MMB无
25MMB槽 2 后
28MFB无
29MFB槽 2 后
图 H.7 24 个可用模板值及各格式的指令槽与 stop;另有 8 个值保留。原图用粗线表示 stop,它可以位于 bundle 内部或末尾。模板 9 依次指定 M、M、I,唯一的 stop 位于本 bundle 与下一个之间;模板 11 使用相同槽类型,但在第一个槽之后再加一个 stop。槽 1 为 L、槽 2 为 X 时使用 L + X 格式。

解答 两个版本见图 H.8。这里的延迟虽与 Itanium 不同,但最常见的 MMF bundle 在 Itanium 中必须独立发射,与本例假定一致。

(a) 使 bundle 数量最少。 每周期至多执行一个 bundle;“末尾 stop”表示槽 2 之后的组边界。

bundle 模板槽 0槽 1槽 2末尾 stop执行周期
9: M M IL.D F0,0(R1)L.D F6,-8(R1)是1
14: M M FL.D F10,-16(R1)L.D F14,-24(R1)ADD.D F4,F0,F2否3
15: M M FL.D F18,-32(R1)L.D F22,-40(R1)ADD.D F8,F6,F2是4
15: M M FL.D F26,-48(R1)S.D F4,0(R1)ADD.D F12,F10,F2是6
15: M M FS.D F8,-8(R1)S.D F12,-16(R1)ADD.D F16,F14,F2是9
15: M M FS.D F16,-24(R1)ADD.D F20,F18,F2是12
15: M M FS.D F20,-32(R1)ADD.D F24,F22,F2是15
15: M M FS.D F24,-40(R1)ADD.D F28,F26,F2是18
16: M I BS.D F28,-48(R1)DADDUI R1,R1,#-56BNE R1,R2,Loop否21

(b) 假定每周期执行一个 bundle,使周期数最少。

bundle 模板槽 0槽 1槽 2末尾 stop执行周期
8: M M IL.D F0,0(R1)L.D F6,-8(R1)否1
9: M M IL.D F10,-16(R1)L.D F14,-24(R1)是2
14: M M FL.D F18,-32(R1)L.D F22,-40(R1)ADD.D F4,F0,F2否3
14: M M FL.D F26,-48(R1)ADD.D F8,F6,F2否4
15: M M FADD.D F12,F10,F2是5
14: M M FS.D F4,0(R1)ADD.D F16,F14,F2否6
14: M M FS.D F8,-8(R1)ADD.D F20,F18,F2否7
15: M M FS.D F12,-16(R1)ADD.D F24,F22,F2是8
14: M M FS.D F16,-24(R1)ADD.D F28,F26,F2否9
9: M M IS.D F20,-32(R1)S.D F24,-40(R1)是11
16: M I BS.D F28,-48(R1)DADDUI R1,R1,#-56BNE R1,R2,Loop否12
图 H.8 x[i] = x[i] + s 展开七次后的 IA-64 指令,包括 bundle 位与 stop。(a) 最小化 bundle 数,(b) 最小化周期数,假定冒险使整个 bundle 停顿。空格是未使用槽,编码为空操作;没有 stop 表示某些 bundle 可以并行执行。(a) 需要 9 个 bundle,(b) 需要 11 个,但后者执行周期仅略多于前者的一半。槽利用率分别为 85% 和 70%。空槽和 bundle 格式可能导致代码远大于其他 RISC 体系结构。两个序列最后一个 bundle 的分支都依赖同一 bundle 中的 DADD;在 IA-64 中,会编码为设置谓词寄存器,再执行以该寄存器为谓词的分支。通常,同一 bundle 不允许这样的依赖,但这属于前述少数例外。

译注:图 H.8 图注把整数加法简称为 DADD,表中实际助记符是 DADDUI。

指令集基础

介绍推测专用支持之前,先简述主要指令编码,并概览五类主要指令 A、I、M、F、B。每条 IA-64 指令长 41 位,高 4 位与 bundle 中指定执行单元槽的位共同构成主操作码。也就是说,4 位操作码在不同执行槽类别中复用,可以把操作码理解为这 4 位加 M、F、I、B 或 L + X 标识。每条指令的低 6 位指定保护该指令的谓词寄存器,下一小节将讨论。

图 H.9 汇总除多媒体指令外的大多数主要格式,并列出各格式编码的指令示例。

指令类型该类格式数代表指令额外操作码位GPR/FPR 数立即数字段其他/说明
A8加、减、AND、OR930 位
A左移并相加730 位2 位移位计数
AALU 立即数操作928 位
A立即数加法3214 位
A立即数加法0222 位
A比较420 位2 个谓词目的寄存器
A立即数比较318 位2 个谓词目的寄存器
I29可变移位量的右移/左移930 位许多多媒体指令使用此格式
I位测试636 位字段指定符2 个谓词目的寄存器
I传送到 BR619 位分支预测分支寄存器指定符
M46整数/浮点加载、存储及缓存行预取1020 位推测/非推测
M整数/浮点加载、存储、缓存行预取,并按立即数后递增928 位推测/非推测
M整数/浮点加载、预取,并按寄存器值后递增103推测/非推测
M整数/浮点推测检查31两字段合计 21 位
B9PC 相对分支、计数分支7021 位
BPC 相对调用4021 位1 个分支寄存器
F15浮点算术24
F浮点比较222 个 6 位谓词寄存器字段
L + X4长立即数传送2164 位
图 H.9 IA-64 ISA 的部分指令格式。主操作码和保护谓词寄存器指定符,为每条指令另增加 10 位。第二列合计 111 种格式,采用严格计数:即使字段宽度相同,只要用途不同也算不同格式;若只统计字段宽度真正不同的格式,数量约为此处的三分之一到二分之一。有些指令的未使用位被保留,表中未列出。立即数位数包含符号位。分支指令包含预测位,供预测器没有有效预测时使用。多媒体指令有许多格式,此处仅展示一种。

译注:表中保留源书的字段数和空白,包括“22 位立即数、2 个 GPR/FPR”一行;按前文通常的寄存器字段宽度直接相加会出现位数疑点,不能仅凭本表推导准确的机器码布局。

谓词化与推测支持

IA-64 提供全面的谓词化支持:几乎每条指令都可以带谓词。谓词寄存器的编号放在指令字段的低 6 位。由于支持范围很广,if 转换和代码移动的开销,比只支持有限条件指令时更低。完全谓词化带来的一个结果是:条件分支不过是一条带保护谓词的分支!

谓词寄存器通过比较或测试指令设置。比较指令指定十种比较测试之一,以及两个目的谓词寄存器。这两个寄存器可以分别写入比较结果(0 或 1)及其反值,也可以写入组合两个测试的某种逻辑函数,例如 AND,以及其反值。这种能力允许一条指令完成多个比较。

IA-64 分别支持两类推测:控制推测负责推迟推测指令的异常;存储器访问推测支持加载指令的推测执行。

推迟异常采用与 poison 位等价的机制。通用寄存器上的位称为 NaT(Not a Thing);多出的这一位使 GPR 实际宽度达到 65 位。浮点寄存器则使用特殊值 NaTVal(Not a Thing Value),以零有效数和超出 IEEE 范围的指数编码。只有推测加载会生成这些值,但所有不影响存储器的指令,都会把 NaT 或 NaTVal 传播到结果寄存器。加载有推测和非推测两种;后者只能立即触发异常,不能推迟。浮点异常不通过这套机制处理,而用浮点状态寄存器记录。

推迟的异常可以通过两种方式解决。第一,如果非推测指令,例如存储,将 NaT 或 NaTVal 作为源操作数,就立即触发不可恢复的异常。第二,可以用 chk.s 检测 NaT 或 NaTVal,并跳转到编译器安排的恢复例程,从推测操作中恢复。这种恢复方式对存储器访问推测更有意义。

如果带有 NaT 或 NaTVal 的内容不能存储,操作系统就无法保存处理器状态。因此,IA-64 提供特殊的寄存器保存与恢复指令:它们不会因 NaT 或 NaTVal 而引发异常,同时会保存和恢复 NaT 位。

IA-64 对存储器访问推测的支持,采用 advanced load,即提前加载:把加载推测性地移到它可能依赖的存储之前。用 ld.a 执行提前加载时,会在称为 ALAT 的特殊表中创建一项,同时记录加载的目的寄存器和访问地址。每条存储执行时,都对有效 ALAT 项进行相联查找;若发现相同地址,就将该项标为无效。

在任何非推测指令,例如存储,使用提前加载的结果或由该结果派生的值之前,必须显式检查。检查指定提前加载的目的寄存器。如果对应 ALAT 项仍有效,推测就合法,检查只需清除该项。若检查失败,则取决于检查形式:ld.c 直接在此处重新从存储器加载数据,适用于只提前加载本身的情况;另一种 chk.a 指定修复例程地址,用于重新执行加载,以及依赖加载值的其他推测代码。

Itanium 2 处理器

Itanium 2 是 IA-64 的第二代实现。第一代 Itanium 1 于 2001 年推出,时钟频率为 800 MHz。原书记载 Itanium 2 于 2003 年首次交付,2005 年最高频率达到 1.6 GHz。两者很相似,流水线结构有一些差异,存储层次结构的差异则更大。Itanium 2 的速度约为 Itanium 1 的四倍,来自频率翻倍、更积极的存储层次结构、增加功能单元以提高指令吞吐量、更完善的旁路、缩短流水线以减少部分停顿,以及更加成熟的编译器系统。

在两代 Itanium 之间大致相同的时间跨度里,Pentium 性能提高到原来的三倍多。相比已经成熟的 IA-32 实现,Itanium 的体系结构和软件系统都较新,因此更大的改进幅度是合理的。

Itanium 2 每周期可取指和发射两个 bundle,至多六条指令。它采用全片上的三级存储层次结构。第一级指令和数据缓存分离,各为 16 KB;浮点数据不放入一级缓存。第二、三级为统一缓存,容量分别为 256 KB 和 3–9 MB。

功能单元与指令发射

Itanium 2 有 11 个功能单元:两个 I-unit、四个 M-unit(两个加载、两个存储)、三个 B-unit、两个 F-unit。所有功能单元都采用流水线。图 H.10 给出典型指令的延迟。此外,结果从一个单元旁路到另一个单元,通常至少增加一个周期。

指令延迟
整数加载1
浮点加载5–9
正确预测为跳转的分支0–3
预测错误的分支6
整数 ALU 操作0
浮点算术4
图 H.10 Itanium 2 上部分典型指令的延迟。这里将延迟定义为两条相互依赖指令之间必须插入的最少指令数。整数加载假定命中一级缓存;浮点加载总是绕过一级缓存,因此延迟等于二级缓存访问时间。有些功能单元还存在少量限制,主要涉及不常见指令。

Itanium 2 每周期从两个 bundle 发射至多六条指令。最坏情况下,如果某个 bundle 在发射时被拆开,硬件可能只看到四条指令:第一个 bundle 剩一条,第二个有三条。指令依据 bundle 位分配到功能单元;这种分配不因其中出现空操作或谓词为假的指令而改变。此外,如果下一条待发射指令需要的单元已被占用,使发射受阻,相应 bundle 就会被拆开。即使只剩一条指令,拆开的 bundle 仍占据两个 bundle 槽中的一个。

Itanium 2 的八级流水线分为四个主要部分:

  • 前端:IPG、Rotate。 每周期向预取缓冲区取至多 32 字节,即两个 bundle。缓冲区最多容纳八个 bundle,共 24 条指令。分支预测使用类似第 3 章所述的多级自适应预测器。
  • 指令分发:EXP、REN。 将至多六条指令分发到 11 个功能单元,并实现寄存器轮转和寄存器栈所需的重命名。
  • 操作数分发:REG。 访问寄存器堆、进行寄存器旁路、访问并更新寄存器记分牌,以及检查谓词依赖。记分牌判断各条指令何时能继续,使 bundle 中某一条指令因缓存未命中等不可预测事件而停顿时,不必让整个 bundle 停顿。图 H.8 已说明,除非精心调度,整包停顿会导致较差性能。
  • 执行:EXE、DET、WRB。 通过 ALU 和加载—存储单元执行指令,检测异常并设置 NaT,退役指令并写回。

Itanium 1 和 Itanium 2 都具有许多通常与第 3 章动态调度流水线联系在一起的机制:动态分支预测、寄存器重命名、记分牌、执行前用于指令对齐和重命名等的多个流水级,以及执行后用于异常检测的多个流水级。虽然这些机制一般比先进动态调度超标量处理器中的对应机制简单,但总体看来,更依赖编译器技术的 Itanium,似乎与第 3 章那些动态调度处理器一样复杂!

既然主要依靠编译时技术寻找并行性,为何还要加入这些特性?原因主要有两个。第一,动态技术有时确实好得多,省去它们会严重损害性能;动态分支预测就是例子。

第二,高性能离不开缓存,而缓存不可避免地产生未命中。未命中难以预测,在当时处理器上耗时也很长。早期 VLIW 处理器发生缓存未命中时,整个处理器会冻结,保持编译器最初规定的同步并行关系。现代处理器的未命中代价可达数十到数百周期,这种方法完全不现实。允许部分指令继续而另一些停顿,就需要引入某种动态调度,这里采用记分牌。若停顿很长,反依赖还会阻碍等待期间的进展。

因此,Itanium 实现也引入寄存器重命名。

Itanium 2 的性能

图 H.11 比较 1.5 GHz Itanium 2 与 Pentium 4、AMD Athlon、IBM Power5 在五个 SPECint 和五个 SPECfp 基准上的性能。就完整 SPEC 浮点套件而言,Itanium 2 略慢于 Power5,比 AMD Athlon 或 Pentium 4 快约 35%。在 SPECint 上,Itanium 2 比 Power5 快 15%,而 AMD Athlon 和 Pentium 4 又都比 Itanium 2 快约 15%。Itanium 2 与 Power5 的功耗和裸片面积都大得多。事实上,Power5 包含两个处理器,普通 SPEC 测试时只启用其中一个,但其晶体管数仍不到 Itanium 的一半。如果移除 Power5 的一个处理器来缩小面积、晶体管数和功耗,那么 Itanium 将明显成为规模最大、功耗最高的处理器。

四款多发射处理器的 SPEC 性能
图 H.11 四款多发射处理器在五个 SPECfp 和五个 SPECint 基准上的性能。时钟频率分别为:Itanium 2,1.5 GHz;Pentium 4 Extreme Edition,3.8 GHz;AMD Athlon 64,2.8 GHz;IBM Power5,1.9 GHz。

图内纵轴 SPEC Ratio 为 SPEC 性能比;横轴依次为 wupwise、galgel、mesa、swim、sixtrack、gcc、gzip、crafty、gap、twolf;图例保留处理器名称。


  1. 译注:原文两处均写“32 到 128”,但也说明共有 128 个寄存器且 0–31 为固定部分,按从零编号应止于 127。 ↩︎ ↩︎

  2. 译注:原文连续两次写“called procedure”。依本段随后关于调用者参数传入被调用者,以及局部区包含调用者输出寄存器的说明,所指应为调用者的输出区;此处显式标明源文疑点。 ↩︎

表格阅读