Xeo*_*Xeo 85 performance assembly history cpu-registers
在32位,我们有8个"通用"寄存器.使用64位,数量翻倍,但它似乎独立于64位变化本身.
现在,如果寄存器如此之快(没有存储器访问),为什么它们自然不存在呢?CPU构建器不应该在CPU中使用尽可能多的寄存器吗?为什么我们只有我们拥有的金额的逻辑限制是什么?
Joh*_*ley 116
你不仅拥有大量寄存器的原因有很多:
这些天我们确实有很多寄存器 - 它们只是没有明确编程.我们有"注册重命名".虽然您只访问一个小集(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]
Run Code Online (Sandbox Code Playgroud)
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]
Run Code Online (Sandbox Code Playgroud)
我认为x86最近有大量重命名的寄存器(ballpark 256).这意味着每个指令只有8位乘以2只是为了说明源和目的地是什么.它将大量增加核心所需的导线数量及其尺寸.因此,大多数设计人员已经确定了16-32个寄存器的最佳位置,对于无序CPU设计,寄存器重命名是缓解它的方法.
编辑:无序执行和寄存器重命名的重要性.一旦你有了OOO,寄存器的数量并不重要,因为它们只是"临时标签"并被重命名为更大的虚拟寄存器集.您不希望数字太小,因为编写小代码序列变得困难.这对于x86-32来说是一个问题,因为有限的8个寄存器意味着很多临时工作最终会通过堆栈,并且核心需要额外的逻辑来转发对内存的读/写.如果你没有OOO,你通常会谈论一个小核心,在这种情况下,一个大的寄存器组是一个很差的成本/性能优势.
因此,寄存器库大小有一个自然的最佳位置,对于大多数类CPU来说,它最多可以容纳32个架构寄存器.x86-32有8个寄存器,它肯定太小了.ARM配备了16个寄存器,这是一个很好的折衷方案.如果有的话,32个寄存器有点太多了 - 你最终不需要最后10个寄存器.
这些都不会触及您为SSE和其他向量浮点协处理器获得的额外寄存器.这些作为额外的集合是有意义的,因为它们独立于整数核心运行,并且不会以指数方式增加CPU的复杂性.
Dig*_*oss 11
因为几乎每条指令都必须选择1,2或3个体系结构可见的寄存器,所以扩展它们的数量会使每条指令的代码大小增加几位,从而降低代码密度.它还增加了必须保存为线程状态的上下文量,并且部分保存在函数的激活记录中. 这些操作经常发生.管道互锁必须检查每个寄存器的记分板,这具有二次时间和空间复杂性.也许最大的原因是简单地与已经定义的指令集兼容.
但事实证明,由于寄存器重命名,我们确实有很多寄存器,我们甚至不需要保存它们.CPU实际上有许多寄存器集,它会在代码执行时自动在它们之间切换.这样做纯粹是为了让你获得更多的寄存器.
例:
load r1, a # x = a
store r1, x
load r1, b # y = b
store r1, y
Run Code Online (Sandbox Code Playgroud)
在仅具有r0-r7的体系结构中,CPU可以自动重写以下代码:
load r1, a
store r1, x
load r10, b
store r10, y
Run Code Online (Sandbox Code Playgroud)
在这种情况下,r10是一个隐藏的寄存器,暂时替换r1.CPU可以告诉第一个存储后r1的值永远不会再次使用.这允许第一负载被延迟(甚至片上高速缓存命中通常需要几个周期),而不需要第二负载或第二存储的延迟.