64 个寄存器或三个操作数指令在汇编级别哪个更有用?

Which is more useful at an assembly level, 64 registers or three operand instructions?

这个问题的背景是为 16 位自制程序编写 C 编译器 CPU。

我有 12 位操作数用于 ALU 指令(例如 ADD、SUB、AND 等)。

我可以为指令提供来自 16 个寄存器的三个操作数或来自 64 个寄存器的两个操作数。

例如

SUB A <- B - C  (registers r0-r15)

SUB A <- A - B  (registers r0-r63)

对于 C 编译器及其作者来说,具有三个操作数指令的 16 个寄存器是否比具有两个操作数指令的 64 个寄存器更有用?

关于寄存器的数量,一般来说,我认为大多数 C 在只有 16 个通用寄存器可用时(如 AMD64)可以编译为高效的机器代码。然而,拥有几个专用于函数参数的寄存器和一些标记为易失性的寄存器可能是有益的——这意味着它们可以在任何函数内部使用,但可以被任何被调用的函数破坏。增加到 32 个寄存器可能是有益的,但我怀疑如果你有 64 个通用寄存器用于常规 16 位CPU,会有很多改进。无论如何,您都必须将要在 C 函数中使用的大多数寄存器的原始内容保存到堆栈中。将一个函数限制为仅同时使用 7 个寄存器(而不是 37 个)对于 C 编译器来说可能仍然更有效(堆栈),即使有更多的寄存器可用。

很大程度上取决于 C calling convention you will be using. Which registers are to be used to pass values from caller to callee, which registers are to be considered volatile, what is the cost of pushing to/popping from the stack, etc. You might win more by using a Register Window for managing your registers and stack usage across function calls. Sun Sparc for example has a register window of 8 completely "local" registers, 8 registers that are shared with the caller and 8 registers that will be shared with any callee function. (Furthermore 8 global registers can be addressed as well.) That way you don't have to worry about pushes to the stack, there will always be a single push of 16 registers for every function call simultaneously to changing the execution pointer and a 16 register pop for every return. Intel ia64 有类似的东西但具有可配置的寄存器 window 大小。

然而,SUB C,A,B 仅比 SUB A,B 稍有优势,因为保留中间结果非常重要(A 需要经常保留)并且简单的寄存器到寄存器副本非常昂贵。在大多数情况下这似乎不太可能。

你会使用单独的浮点或定点寄存器吗?

具有非破坏性 3 操作数指令的 16 个寄存器可能更好。

但是,您还应该考虑用这些指令位做一些其他有趣的事情。对于自制软件,您可能不关心为将来的扩展保留任何内容,也不想添加大量额外的操作码 .

ARM 采用了一种有趣的方法,让每条指令的一个操作数都经过 the barrel shifter,因此每条指令都是免费的 "shift-and-whatever" 指令。即使在 "thumb" 模式下也支持此功能,其中最常见的指令只有 16 位。 (ARM 模式具有传统的 RISC 32 位固定指令大小。它将其中的 4 位专用于每条指令的谓词执行。)


我记得看到过一项关于通过将理论架构中的寄存器数量加倍来提高性能的研究,用于 SPECint 或其他东西。 8->16 可能是 5 或 10%,16->32 只有几个 %,而 32->64 甚至更小。

所以 16 个整数寄存器在大多数情况下是 "enough",除非您经常使用 int32_t,因为每个这样的值将占用两个 16 位寄存器。 x86-64 只有 16 个 GP 寄存器,并且大多数函数可以非常舒适地在寄存器中保存它们的大量状态。即使在进行函数调用的循环中,ABI 中也有足够的调用保留寄存器,因此 spill/reload 通常不必在循环中发生。

3 操作数指令在代码大小和指令数方面的收益将比节省偶尔的溢出/重新加载更大。 gcc 输出必须始终为 mov,并使用 lea 作为非破坏性添加/移位。


如果您想针对软件流水线优化 CPU 以隐藏内存加载延迟(), more registers are great, esp. if you don't have register renaming. However, I'm not sure how good compilers are at static instruction scheduling。这不再是热门话题,因为所有高性能 CPU 都是乱序。(OTOH,人们实际使用的很多软件是 运行 在智能手机中的有序 ARM CPUs 上。)我没有尝试让编译器优化的经验对于有序的 CPUs,所以我知道依赖它是多么可行。

如果您的 CPU 非常简单,以至于在负载运行期间无法执行任何其他操作,那么这可能无关紧要。 (这真的很麻烦,因为我对简单设计的实用性知之甚少。甚至 "simple" 有序的现代 CPU 也是流水线化的。)


64 个寄存器正在进入 "too many" 领域,其中 saving/restoring 它们需要大量代码。内存量可能仍然可以忽略不计,但由于您不能遍历寄存器,因此需要 64 条指令。


如果您要从头开始设计 ISA,请查看 Agner Fog's CRISC proposal 和由此产生的讨论。您的目标非常不同(高性能/功率预算 64 位 CPU 与简单的 16 位),因此您的 ISA 当然会非常不同。然而,讨论可能会让你想到你没有考虑过的事情,或者你想尝试的想法。