是否有任何体系结构对标量整数和浮点运算使用相同的寄存器space?
Is there any architecture that uses the same register space for scalar integer and floating point operations?
我见过的大多数支持本机标量硬件 FP 的架构都将它们推到一个完全独立的寄存器中 space,与主要的寄存器集分开。
我见过的大多数支持本机标量硬件 FP 的架构都将它们推到一个完全独立的寄存器中 space,与主要的寄存器集分开。
- X86 的遗留 x87 FPU 使用部分独立的浮点 "stack machine"(读作:基本上是一个固定大小的 8 项环形缓冲区),寄存器
st(0)
到 st(7)
用于索引每个物品。这可能是最流行的。它只能通过 load/store 到内存,或者通过将比较结果发送到 EFLAGS 来与其他寄存器进行交互。 (286fnstsw ax
, and i686 fcomi
).
- 支持 FPU 的 ARM 有一个单独的 FP 寄存器 space,其工作方式与其整数 space 类似。主要区别在于专门用于浮点的单独指令集,但即使是习语也大多是一致的。
- MIPS 介于两者之间,因为 floating point 在技术上是通过协处理器完成的(至少在视觉上是这样),并且它的使用规则略有不同(比如使用两个浮点寄存器而不是单个扩展寄存器的双精度数寄存器),但它们在其他方面的工作方式与 ARM 非常相似。
- X86 较新的 SSE 标量指令的运行方式与其矢量指令类似,使用类似的助记符和习语。它可以自由加载和存储到标准寄存器和内存,您可以使用 64 位内存引用作为许多标量运算的操作数,例如
addsd xmm1, m64
or subsd xmm1, m64
, but you can only load from and store to registers via movq xmm1, r/m64
, movq r/m64, xmm1
, and friends。这类似于 ARM64 NEON,尽管它与 ARM 的标准标量指令集略有不同。
相反,许多向量化指令甚至不理会这种区别,只是在标量和向量之间进行区分。对于 x86、ARM 和 MIPS 这三者:
- 它们将标量和向量寄存器分开 spaces。
- 他们重复使用相同的寄存器 space 进行矢量化整数和浮点运算。
- 他们仍然可以访问适用的整数堆栈。
- 标量运算只需从相关寄存器space(或 x86 FP 常量情况下的内存)中提取标量。
但我想知道:是否有任何CPU架构重用相同的寄存器space进行整数和浮点运算?
如果不是(由于兼容性以外的原因),什么会阻止硬件设计师选择走那条路?
摩托罗拉 88100 有一个用于浮点和整数值的寄存器文件(31 个 32 位条目加上一个硬连线零寄存器)。对于 32 位寄存器和双精度支持,必须使用寄存器对来提供值,这极大地限制了可以保存在寄存器中的双精度值的数量。
后续的 88110 添加了 32 个 80 位扩展寄存器,用于附加(和更大)浮点值。
参与摩托罗拉 88k 开发的 Mitch Alsup 开发了自己的加载存储 ISA(至少部分出于教学原因),如果我没记错的话,它使用统一的寄存器文件。
还应注意,Power ISA(PowerPC 的后代)定义了一个 "Embedded Floating Point Facility",它使用 GPR 来表示浮点值。这减少了核心实现成本和上下文切换开销。
单独的寄存器文件的一个好处是它提供了显式存储以减少直接有限超标量设计中的寄存器端口数(例如,为每个文件提供三个读取端口将允许所有对一个 FP,甚至三源-操作数 FMADD,和一个并行启动的基于 GPR 的操作和许多常见的基于 GPR 的操作与具有单个寄存器文件的五个读取端口相比,以支持 FMADD 和另一个双源操作)。另一个因素是容量是额外的并且与宽度无关;这既有优点也有缺点。此外,通过将存储与操作相结合,可以以更直接的方式实现高度不同的协处理器。考虑到芯片尺寸限制,这对于早期微处理器来说更为重要,但 UltraSPARC T1 与八核共享一个浮点单元,而 AMD 的 Bulldozer 与两个整数 "cores".
共享一个 FP/SIMD 单元。
统一的寄存器文件有一些调用约定的优点;无论值的类型如何,值都可以在相同的寄存器中传递。统一的寄存器文件还允许所有寄存器用于所有操作,从而减少了无法使用的资源。
当然,从历史上看,FPU 是 CPU 的可选部分(因此有芯片版本 with/without FPU)。或者它可以是一个可选的独立芯片(例如 8086 + 8087 / 80286 + 80287 / ...),因此 FPU 拥有自己的独立寄存器非常有意义。
当你制作一个 CPU.
因此历史上一直有使用单独的 FP 寄存器的先例。
但对于一个全新的蓝天设计来说,这是一个有趣的问题。如果您要拥有一个 FPU,则必须集成它以便在 FP 比较和类似的分支上进行分支时获得良好的性能。 为 64 位整数共享相同的寄存器/double
是完全合理的从软件和硬件的角度来看。
但是,对于现代高性能 CPU,某种类型的 SIMD 也是必需的。 CPU-SIMD(与 GPU 风格相反)通常使用短的固定宽度向量寄存器完成,通常为 16 字节宽,但最近英特尔已扩大到 32 或 64 字节。对于 64 位标量整数寄存器,仅使用低 8 字节会留下很多浪费 space(当 reading/writing 整数代码时可能会消耗电量)。
当然,在 GP 整数和 SIMD 向量寄存器之间移动数据需要指令,如果值得硬件成本,那么在整数和 SIMD 之间共享一个寄存器集会很好。
最好的情况是假设一个全新的带有标量 FPU 的 ISA,特别是如果它只是一个 FPU 并且没有整数 SIMD . 即使在这种不太可能的情况下,仍然有一些原因:
指令编码space
独立架构寄存器的一个重要原因是指令编码 space / 位。
对于为每个操作数选择 16 个寄存器的指令,每个操作数需要 4 位。您宁愿有 16 个 FP 和 16 个整数寄存器,还是总共 16 个相互竞争寄存器分配变量的寄存器?
大量使用 FP 的代码通常至少需要一些整数寄存器用于指向数组的指针和循环控制,因此拥有单独的整数寄存器并不意味着它们都“浪费”在 FP 循环中。
即对于相同的指令编码格式,选择是在 N 个整数 和 N 个 FP 寄存器与 N 个灵活寄存器之间,而不是 2N 个灵活寄存器。因此,通过将它们在 FP 和 int 之间拆分,您可以获得两倍的独立寄存器总数。
32 个灵活的寄存器对于很多代码来说可能就足够了,许多真正的 ISA 确实有 32 个架构寄存器(AArch64、MIPS、RISC-V、POWER、许多其他 RISC)。每条指令需要 10 或 15 位(每条指令 2 或 3 个操作数,如 add dst, src
或 add dst, src1, src2
)。不过,只有 16 个灵活寄存器 绝对 比每个寄存器有 16 个更糟糕。在对函数使用多项式逼近的算法中,您通常需要在寄存器中使用大量 FP 常量,并且不会留下很多用于展开以隐藏 FP 指令的延迟。
总结:32 combined/flexible regs 对于软件来说通常比 16 int + 16 fp 更好,但这会花费额外的指令位。 16 个 flexible regs 会比 16 int + 16 FP 差很多,运行 在某些 FP 代码中更糟糕的寄存器压力。
中断处理程序通常必须保存所有整数寄存器,但内核代码通常仅使用整数指令构建。因此,如果中断处理程序必须 save/restore 32 个组合寄存器的完整宽度,而不是仅仅 16 个整数寄存器,那么中断延迟会更糟。他们可能仍然能够跳过 save/restore 的 FPU control/status regs.
(中断处理程序仅 需要 保存它实际修改的寄存器,或者如果调用 C,则调用破坏的寄存器。但是 OS 像 Linux 倾向于在进入内核时保存所有整数寄存器,因此它在一个地方保存了一个线程的状态,用于处理修改另一个 process/thread 状态的 ptrace
系统调用。至少它在系统调用入口点执行此操作;关于中断处理程序的 IDK。)
如果我们谈论的是 32int + 32fp 与 32 灵活的 regs,并且组合的 regs 仅适用于标量 double
或 float
,那么这个论点并不适用。
说到调用约定,当您使用任何 FP 寄存器时,您往往会使用很多,通常是在没有非内联函数调用的循环中。有很多调用破坏的 FP 寄存器是有意义的。
但对于整数,您往往希望调用破坏与调用保留的均匀混合,因此您可以在没有 saving/restoring 的小函数中使用一些暂存 regs,但也有很多 regs在频繁调用函数时保留内容。
不过,拥有一组寄存器会简化调用约定。 讨论了更多关于调用约定的权衡(过多的调用破坏与过多的调用保留。)如果只有一个平面寄存器,则 XMM 寄存器中有关整数的内容将不适用 space , 不过
CPU 物理设计注意事项
这是另一组主要原因。
首先,我假设一个高性能乱序设计具有大型物理寄存器文件,架构寄存器是renamed onto. (See also my answer on )。
正如@PaulClayton 的回答所指出的,将物理寄存器文件拆分为整数和 FP 减少了每个寄存器中对 read/write 端口的需求。您可以提供 3 源 FMA 指令,而不必提供任何 3 输入整数指令。
(Intel Haswell就是一个例子:adc
和cmovcc
仍然是2微指令,但是FMA是1。Broadwell把adc和cmov也做成了单微指令。这不是清除寄存器读取是否是瓶颈 in this loop that runs 7 unfused-domain uops per clock on Skylake,但在 Haswell 上只有 6.25。将某些指令从只写目标更改为读+写并添加索引寻址模式(blsi ebx, [rdi]
到 add ebx, [rdi+r8]
.) 后一个版本在 Haswell 上每时钟运行约 5.7 次寄存器读取,或在 Skylake 上运行约 7.08 次,与快速版本相同,表明 Skylake 可能在每时钟约 7 次寄存器读取上遇到瓶颈。现代 x86 微体系结构非常复杂并且有很多事情要做,所以我们不能从中得出太多结论,特别是因为最大 FP uop 吞吐量几乎与最大整数 uop 吞吐量一样高。)
然而,Haswell/Skylake 没有问题 运行 4x add reg, reg
,每个时钟读取 8 个寄存器并写入 4 个。前面的示例主要用于读取“冷”寄存器也没有写,但重复 4xadd
将只读取 4 个冷寄存器(或 1 个冷寄存器 4 次)作为源。鉴于有限的寄存器,目的地最多只在几个周期前写入,因此可能会被绕过转发。
我不确切知道我在 Agner Fog 博客上的示例中的瓶颈在哪里,但它似乎不太可能是 只是 整数寄存器读取。可能也与尝试最大化未融合域 uops 有关。
芯片上的物理距离是另一个主要因素:您想将 FP 寄存器文件物理地放置在 FP 执行单元附近,以减少获取时的功耗和光速延迟操作数。 FP 寄存器文件具有更大的条目(假设为 SIMD),因此减少所需的端口数量可以节省空间或启动对那么多数据位的访问。)
将 FP 执行单元保留在 CPU 的一部分可以使 FP 操作之间的转发比 FP->integer 更快。 (旁路延迟)。 x86 CPUs 保持 SIMD/FP 和整数非常紧密地耦合,在标量和 FP 之间传输数据的成本很低。但是一些 ARM CPUs 基本上停止了 FP->int 的管道,所以我猜通常它们的交互更松散。作为硬件设计的一般规则,两个快速的小东西通常比一个快速的大东西便宜/低功耗。
Agner Fog 的 Proposal for an ideal extensible instruction set (now on Github and called ForwardCom) 引发了一些关于如何设计 ISA 的非常有趣的讨论,包括这个问题。
他最初的提议是统一r0..r31
一组架构寄存器,每个 128 位,支持高达 64 位(可选 128 位)和 single/double(可选 quad)的整数计划生育。也可用作谓词寄存器(而不是具有 FLAGS)。它们也可以用作 SIMD 向量,可选硬件支持大于 128 位的向量,因此可以编写/编译软件以在将来自动利用更宽的向量。
出于上述原因,评论者建议将向量寄存器与标量分开。
具体来说,Hubert Lamontagne commented:
Registers:
As far as I can tell, separate register files are GOOD. The reason for this is that as you add more read and write ports to a register file, its size grows quadratically (or worse). This makes cpu components larger, which increases propagation time, and increases fanout, and multiplies the complexity of the register renamer. If you give floating point operands their own register file, then aside from load/store, compare and conversion operations, the FPU never has to interact with the rest of the core. So for the same amount of IPC, say, 2 integer 2 float per cycle, separating float operations means you go from a monstruous 8-read 4-write register file and renaming mechanism where both integer ALUs and FP ALUs have to be wired everywhere, to a 2-issue integer unit and a 2-issue FPU. The FPU can have its own register renaming unit, its own scheduler, its own register file, its own writeback unit, its own calculation latencies, and FPU ALUs can be directly wired to the registers, and the whole FPU can live on a different section of the chip. The front end can simply recognize which ops are FPU and queue them there. The same applies to SIMD.
进一步的讨论表明,将标量浮点数与向量浮点数分开是愚蠢的,SIMD int 和 FP 应该放在一起,但是专用的标量整数本身确实有意义,因为分支和索引是特殊的。 (即完全像当前的 x86,除了标量整数之外的所有内容都在 XMM/YMM/ZMM 寄存器中完成。)
我认为这是阿格纳最终做出的决定。
如果您只考虑标量 float 和标量 int,那么统一体系结构寄存器的情况更多,但出于硬件设计原因,它有很多将它们分开是有意义的。
如果您对为什么 ISA 是这样设计的感兴趣,以及如果我们有一个干净的石板会有什么更好的,我强烈建议您通读整个讨论主题,如果您有足够的背景知识理解要点。
CDC 6600 and Cray 1, both Seymour Cray designs, used a zero exponent to indicate an integer, a kind of tagged architecture。这意味着一个有限的整数范围,但一个统一的浮点/整数寄存器集。
此外,x87 和 MMX 共享寄存器。
刚刚通过搜索偶然发现了这一点,但我要补充一点,Digital VAX 架构使用通用寄存器进行浮点运算。
我见过的大多数支持本机标量硬件 FP 的架构都将它们推到一个完全独立的寄存器中 space,与主要的寄存器集分开。
我见过的大多数支持本机标量硬件 FP 的架构都将它们推到一个完全独立的寄存器中 space,与主要的寄存器集分开。
- X86 的遗留 x87 FPU 使用部分独立的浮点 "stack machine"(读作:基本上是一个固定大小的 8 项环形缓冲区),寄存器
st(0)
到st(7)
用于索引每个物品。这可能是最流行的。它只能通过 load/store 到内存,或者通过将比较结果发送到 EFLAGS 来与其他寄存器进行交互。 (286fnstsw ax
, and i686fcomi
). - 支持 FPU 的 ARM 有一个单独的 FP 寄存器 space,其工作方式与其整数 space 类似。主要区别在于专门用于浮点的单独指令集,但即使是习语也大多是一致的。
- MIPS 介于两者之间,因为 floating point 在技术上是通过协处理器完成的(至少在视觉上是这样),并且它的使用规则略有不同(比如使用两个浮点寄存器而不是单个扩展寄存器的双精度数寄存器),但它们在其他方面的工作方式与 ARM 非常相似。
- X86 较新的 SSE 标量指令的运行方式与其矢量指令类似,使用类似的助记符和习语。它可以自由加载和存储到标准寄存器和内存,您可以使用 64 位内存引用作为许多标量运算的操作数,例如
addsd xmm1, m64
orsubsd xmm1, m64
, but you can only load from and store to registers viamovq xmm1, r/m64
,movq r/m64, xmm1
, and friends。这类似于 ARM64 NEON,尽管它与 ARM 的标准标量指令集略有不同。
相反,许多向量化指令甚至不理会这种区别,只是在标量和向量之间进行区分。对于 x86、ARM 和 MIPS 这三者:
- 它们将标量和向量寄存器分开 spaces。
- 他们重复使用相同的寄存器 space 进行矢量化整数和浮点运算。
- 他们仍然可以访问适用的整数堆栈。
- 标量运算只需从相关寄存器space(或 x86 FP 常量情况下的内存)中提取标量。
但我想知道:是否有任何CPU架构重用相同的寄存器space进行整数和浮点运算?
如果不是(由于兼容性以外的原因),什么会阻止硬件设计师选择走那条路?
摩托罗拉 88100 有一个用于浮点和整数值的寄存器文件(31 个 32 位条目加上一个硬连线零寄存器)。对于 32 位寄存器和双精度支持,必须使用寄存器对来提供值,这极大地限制了可以保存在寄存器中的双精度值的数量。
后续的 88110 添加了 32 个 80 位扩展寄存器,用于附加(和更大)浮点值。
参与摩托罗拉 88k 开发的 Mitch Alsup 开发了自己的加载存储 ISA(至少部分出于教学原因),如果我没记错的话,它使用统一的寄存器文件。
还应注意,Power ISA(PowerPC 的后代)定义了一个 "Embedded Floating Point Facility",它使用 GPR 来表示浮点值。这减少了核心实现成本和上下文切换开销。
单独的寄存器文件的一个好处是它提供了显式存储以减少直接有限超标量设计中的寄存器端口数(例如,为每个文件提供三个读取端口将允许所有对一个 FP,甚至三源-操作数 FMADD,和一个并行启动的基于 GPR 的操作和许多常见的基于 GPR 的操作与具有单个寄存器文件的五个读取端口相比,以支持 FMADD 和另一个双源操作)。另一个因素是容量是额外的并且与宽度无关;这既有优点也有缺点。此外,通过将存储与操作相结合,可以以更直接的方式实现高度不同的协处理器。考虑到芯片尺寸限制,这对于早期微处理器来说更为重要,但 UltraSPARC T1 与八核共享一个浮点单元,而 AMD 的 Bulldozer 与两个整数 "cores".
共享一个 FP/SIMD 单元。统一的寄存器文件有一些调用约定的优点;无论值的类型如何,值都可以在相同的寄存器中传递。统一的寄存器文件还允许所有寄存器用于所有操作,从而减少了无法使用的资源。
当然,从历史上看,FPU 是 CPU 的可选部分(因此有芯片版本 with/without FPU)。或者它可以是一个可选的独立芯片(例如 8086 + 8087 / 80286 + 80287 / ...),因此 FPU 拥有自己的独立寄存器非常有意义。
当你制作一个 CPU.
因此历史上一直有使用单独的 FP 寄存器的先例。
但对于一个全新的蓝天设计来说,这是一个有趣的问题。如果您要拥有一个 FPU,则必须集成它以便在 FP 比较和类似的分支上进行分支时获得良好的性能。 为 64 位整数共享相同的寄存器/double
是完全合理的从软件和硬件的角度来看。
但是,对于现代高性能 CPU,某种类型的 SIMD 也是必需的。 CPU-SIMD(与 GPU 风格相反)通常使用短的固定宽度向量寄存器完成,通常为 16 字节宽,但最近英特尔已扩大到 32 或 64 字节。对于 64 位标量整数寄存器,仅使用低 8 字节会留下很多浪费 space(当 reading/writing 整数代码时可能会消耗电量)。
当然,在 GP 整数和 SIMD 向量寄存器之间移动数据需要指令,如果值得硬件成本,那么在整数和 SIMD 之间共享一个寄存器集会很好。
最好的情况是假设一个全新的带有标量 FPU 的 ISA,特别是如果它只是一个 FPU 并且没有整数 SIMD . 即使在这种不太可能的情况下,仍然有一些原因:
指令编码space
独立架构寄存器的一个重要原因是指令编码 space / 位。
对于为每个操作数选择 16 个寄存器的指令,每个操作数需要 4 位。您宁愿有 16 个 FP 和 16 个整数寄存器,还是总共 16 个相互竞争寄存器分配变量的寄存器?
大量使用 FP 的代码通常至少需要一些整数寄存器用于指向数组的指针和循环控制,因此拥有单独的整数寄存器并不意味着它们都“浪费”在 FP 循环中。
即对于相同的指令编码格式,选择是在 N 个整数 和 N 个 FP 寄存器与 N 个灵活寄存器之间,而不是 2N 个灵活寄存器。因此,通过将它们在 FP 和 int 之间拆分,您可以获得两倍的独立寄存器总数。
32 个灵活的寄存器对于很多代码来说可能就足够了,许多真正的 ISA 确实有 32 个架构寄存器(AArch64、MIPS、RISC-V、POWER、许多其他 RISC)。每条指令需要 10 或 15 位(每条指令 2 或 3 个操作数,如 add dst, src
或 add dst, src1, src2
)。不过,只有 16 个灵活寄存器 绝对 比每个寄存器有 16 个更糟糕。在对函数使用多项式逼近的算法中,您通常需要在寄存器中使用大量 FP 常量,并且不会留下很多用于展开以隐藏 FP 指令的延迟。
总结:32 combined/flexible regs 对于软件来说通常比 16 int + 16 fp 更好,但这会花费额外的指令位。 16 个 flexible regs 会比 16 int + 16 FP 差很多,运行 在某些 FP 代码中更糟糕的寄存器压力。
中断处理程序通常必须保存所有整数寄存器,但内核代码通常仅使用整数指令构建。因此,如果中断处理程序必须 save/restore 32 个组合寄存器的完整宽度,而不是仅仅 16 个整数寄存器,那么中断延迟会更糟。他们可能仍然能够跳过 save/restore 的 FPU control/status regs.
(中断处理程序仅 需要 保存它实际修改的寄存器,或者如果调用 C,则调用破坏的寄存器。但是 OS 像 Linux 倾向于在进入内核时保存所有整数寄存器,因此它在一个地方保存了一个线程的状态,用于处理修改另一个 process/thread 状态的 ptrace
系统调用。至少它在系统调用入口点执行此操作;关于中断处理程序的 IDK。)
如果我们谈论的是 32int + 32fp 与 32 灵活的 regs,并且组合的 regs 仅适用于标量 double
或 float
,那么这个论点并不适用。
说到调用约定,当您使用任何 FP 寄存器时,您往往会使用很多,通常是在没有非内联函数调用的循环中。有很多调用破坏的 FP 寄存器是有意义的。
但对于整数,您往往希望调用破坏与调用保留的均匀混合,因此您可以在没有 saving/restoring 的小函数中使用一些暂存 regs,但也有很多 regs在频繁调用函数时保留内容。
不过,拥有一组寄存器会简化调用约定。
CPU 物理设计注意事项
这是另一组主要原因。
首先,我假设一个高性能乱序设计具有大型物理寄存器文件,架构寄存器是renamed onto. (See also my answer on
正如@PaulClayton 的回答所指出的,将物理寄存器文件拆分为整数和 FP 减少了每个寄存器中对 read/write 端口的需求。您可以提供 3 源 FMA 指令,而不必提供任何 3 输入整数指令。
(Intel Haswell就是一个例子:adc
和cmovcc
仍然是2微指令,但是FMA是1。Broadwell把adc和cmov也做成了单微指令。这不是清除寄存器读取是否是瓶颈 in this loop that runs 7 unfused-domain uops per clock on Skylake,但在 Haswell 上只有 6.25。将某些指令从只写目标更改为读+写并添加索引寻址模式(blsi ebx, [rdi]
到 add ebx, [rdi+r8]
.) 后一个版本在 Haswell 上每时钟运行约 5.7 次寄存器读取,或在 Skylake 上运行约 7.08 次,与快速版本相同,表明 Skylake 可能在每时钟约 7 次寄存器读取上遇到瓶颈。现代 x86 微体系结构非常复杂并且有很多事情要做,所以我们不能从中得出太多结论,特别是因为最大 FP uop 吞吐量几乎与最大整数 uop 吞吐量一样高。)
然而,Haswell/Skylake 没有问题 运行 4x add reg, reg
,每个时钟读取 8 个寄存器并写入 4 个。前面的示例主要用于读取“冷”寄存器也没有写,但重复 4xadd
将只读取 4 个冷寄存器(或 1 个冷寄存器 4 次)作为源。鉴于有限的寄存器,目的地最多只在几个周期前写入,因此可能会被绕过转发。
我不确切知道我在 Agner Fog 博客上的示例中的瓶颈在哪里,但它似乎不太可能是 只是 整数寄存器读取。可能也与尝试最大化未融合域 uops 有关。
芯片上的物理距离是另一个主要因素:您想将 FP 寄存器文件物理地放置在 FP 执行单元附近,以减少获取时的功耗和光速延迟操作数。 FP 寄存器文件具有更大的条目(假设为 SIMD),因此减少所需的端口数量可以节省空间或启动对那么多数据位的访问。)
将 FP 执行单元保留在 CPU 的一部分可以使 FP 操作之间的转发比 FP->integer 更快。 (旁路延迟)。 x86 CPUs 保持 SIMD/FP 和整数非常紧密地耦合,在标量和 FP 之间传输数据的成本很低。但是一些 ARM CPUs 基本上停止了 FP->int 的管道,所以我猜通常它们的交互更松散。作为硬件设计的一般规则,两个快速的小东西通常比一个快速的大东西便宜/低功耗。
Agner Fog 的 Proposal for an ideal extensible instruction set (now on Github and called ForwardCom) 引发了一些关于如何设计 ISA 的非常有趣的讨论,包括这个问题。
他最初的提议是统一r0..r31
一组架构寄存器,每个 128 位,支持高达 64 位(可选 128 位)和 single/double(可选 quad)的整数计划生育。也可用作谓词寄存器(而不是具有 FLAGS)。它们也可以用作 SIMD 向量,可选硬件支持大于 128 位的向量,因此可以编写/编译软件以在将来自动利用更宽的向量。
出于上述原因,评论者建议将向量寄存器与标量分开。
具体来说,Hubert Lamontagne commented:
Registers:
As far as I can tell, separate register files are GOOD. The reason for this is that as you add more read and write ports to a register file, its size grows quadratically (or worse). This makes cpu components larger, which increases propagation time, and increases fanout, and multiplies the complexity of the register renamer. If you give floating point operands their own register file, then aside from load/store, compare and conversion operations, the FPU never has to interact with the rest of the core. So for the same amount of IPC, say, 2 integer 2 float per cycle, separating float operations means you go from a monstruous 8-read 4-write register file and renaming mechanism where both integer ALUs and FP ALUs have to be wired everywhere, to a 2-issue integer unit and a 2-issue FPU. The FPU can have its own register renaming unit, its own scheduler, its own register file, its own writeback unit, its own calculation latencies, and FPU ALUs can be directly wired to the registers, and the whole FPU can live on a different section of the chip. The front end can simply recognize which ops are FPU and queue them there. The same applies to SIMD.
进一步的讨论表明,将标量浮点数与向量浮点数分开是愚蠢的,SIMD int 和 FP 应该放在一起,但是专用的标量整数本身确实有意义,因为分支和索引是特殊的。 (即完全像当前的 x86,除了标量整数之外的所有内容都在 XMM/YMM/ZMM 寄存器中完成。)
我认为这是阿格纳最终做出的决定。
如果您只考虑标量 float 和标量 int,那么统一体系结构寄存器的情况更多,但出于硬件设计原因,它有很多将它们分开是有意义的。
如果您对为什么 ISA 是这样设计的感兴趣,以及如果我们有一个干净的石板会有什么更好的,我强烈建议您通读整个讨论主题,如果您有足够的背景知识理解要点。
CDC 6600 and Cray 1, both Seymour Cray designs, used a zero exponent to indicate an integer, a kind of tagged architecture。这意味着一个有限的整数范围,但一个统一的浮点/整数寄存器集。
此外,x87 和 MMX 共享寄存器。
刚刚通过搜索偶然发现了这一点,但我要补充一点,Digital VAX 架构使用通用寄存器进行浮点运算。