如果寄存器如此快速,为什么我们不能拥有更多?

时间:2011-05-21 02:23:35

标签: performance history cpu-registers assembly

在32位,我们有8个“通用”寄存器。使用64位,数量翻倍,但它似乎与64位变化本身无关 现在,如果寄存器如此之快(没有存储器访问),为什么它们自然不存在呢? CPU构建器不应该在CPU中使用尽可能多的寄存器吗?为什么我们只有我们拥有的金额的逻辑限制是什么?

4 个答案:

答案 0 :(得分:112)

有很多原因你不仅拥有大量的寄存器:

  • 它们与大多数管道阶段密切相关。对于初学者,您需要跟踪他们的生命周期,并将结果转发回以前的阶段。复杂性很快变得难以处理,并且涉及的电线数量(字面上)以相同的速率增长。它在面积上很昂贵,这最终意味着它在一定时间点后在功率,价格和性能方面都很昂贵。
  • 占用指令编码空间。如果有3个操作数指令(例如ARM),则16个寄存器占用源位和目标4位,另外4个占用4位。这是为了指定寄存器而占用的大量指令集编码空间。这最终会影响解码,代码大小和复杂性。
  • 有更好的方法可以达到相同的效果......

这些天我们确实有很多寄存器 - 它们只是没有明确编程。我们有“注册重命名”。虽然您只访问一个小集(8-32个寄存器),但它们实际上是由更大的集合(例如64-256)支持的。然后,CPU跟踪每个寄存器的可见性,并将它们分配给重命名的集合。例如,您可以连续多次加载,修改,然后存储到寄存器,并根据缓存未命中等实际执行每个操作。在ARM中:

ldr r0, [r4]
add r0, r0, #1
str r0, [r4]
ldr r0, [r5]
add r0, r0, #1
str r0, [r5]

Cortex A9内核进行寄存器重命名,因此第一次加载到“r0”实际上转到重命名的虚拟寄存器 - 让我们称之为“v0”。加载,增量和存储发生在“v0”上。同时,我们还对r0执行加载/修改/存储,但是这将被重命名为“v1”,因为这是一个使用r0的完全独立的序列。假设由于高速缓存未命中,“r4”中指针的负载停止。没关系 - 我们不需要等待“r0”做好准备。因为它被重命名,我们可以使用“v1”运行下一个序列(也映射到r0) - 也许这是一个缓存命中,我们只是获得了巨大的性能胜利。

ldr v0, [v2]
add v0, v0, #1
str v0, [v2]
ldr v1, [v3]
add v1, v1, #1
str v1, [v3]

我认为x86最近有大量重命名的寄存器(ballpark 256)。这意味着每个指令的8位乘以2只是为了说明源和目的地是什么。它将大量增加核心所需的导线数量及其尺寸。因此,大多数设计人员已经确定了大约16-32个寄存器的最佳位置,对于无序CPU设计,寄存器重命名是减轻它的方法。

编辑:无序执行和注册重命名的重要性。一旦你有了OOO,寄存器的数量并不重要,因为它们只是“临时标签”并被重命名为更大的虚拟寄存器集。您不希望数字太小,因为编写小代码序列变得困难。这对于x86-32来说是个问题,因为有限的8个寄存器意味着很多临时工作最终会通过堆栈,并且核心需要额外的逻辑来转发对内存的读/写。如果你没有OOO,你通常会谈论一个小核心,在这种情况下,一个大的寄存器组是一个很差的成本/性能优势。

因此,寄存器库大小有一个自然的最佳点,对于大多数类CPU来说,它最多可以容纳32个架构寄存器。 x86-32有8个寄存器,它肯定太小了。 ARM配备了16个寄存器,这是一个很好的折衷方案。如果有的话,32个寄存器有点太多了 - 你最终不需要最后10个寄存器。

这些都不会触及您为SSE和其他向量浮点协处理器获得的额外寄存器。这些是有意义的,因为它们独立于整数核心运行,并且不会以指数方式增加CPU的复杂性。

答案 1 :(得分:10)

我们有更多他们

因为几乎每条指令都必须选择1,2或3个体系结构可见的寄存器,所以扩展它们的数量会使每条指令的代码大小增加几位,从而降低代码密度。它还会增加必须保存为线程状态的 context 的数量,并部分保存在函数的 activation record中。这些操作经常发生。管道互锁必须检查每个寄存器的记分板,这具有二次时间和空间复杂性。也许最大的原因是简单地与已经定义的指令集兼容。

但事实证明,感谢 register renaming我们确实有很多可用的寄存器,我们甚至不需要保存它们。 CPU实际上有许多寄存器集,它会在代码执行时自动在它们之间切换。这样做纯粹是为了让你获得更多的注册。

示例:

load  r1, a  # x = a
store r1, x
load  r1, b  # y = b
store r1, y

在只有r0-r7的架构中,CPU可以自动重写以下代码:

load  r1, a
store r1, x
load  r10, b
store r10, y

在这种情况下,r10是一个隐藏的寄存器,暂时替换r1。 CPU可以告诉第一个存储后r1的值永远不会再次使用。这允许第一次加载延迟(即使片上高速缓存命中通常需要几个周期),而不需要第二次加载或第二次加载的延迟。

答案 2 :(得分:2)

它们一直在添加寄存器,但它们通常与专用指令(例如SIMD,SSE2等)相关联,或者需要编译到特定的CPU架构,这会降低可移植性。现有指令通常适用于特定寄存器,如果可用,则无法利用其他寄存器。传统指令集和所有。

答案 3 :(得分:1)

要在这里添加一些有趣的信息,你会发现有8个相同大小的寄存器允许操作码与十六进制表示法保持一致。例如,指令push ax在x86上是操作码0x50,在最后一个寄存器di上是0x57。然后指令pop ax从0x58开始并上升到0x5F pop di以完成第一个基数-16。保持十六进制一致性,每个大小有8个寄存器。