如果登记册太快了,为什么我们没有更多呢?
在32位,我们有8个“通用”寄存器。 与64位,金额翻倍,但它似乎独立于64位变化本身。
现在,如果寄存器速度如此之快(没有内存访问),为什么自然不会有更多? CPU制造商不应该将尽可能多的寄存器工作到CPU中吗? 为什么我们只有我们拥有的金额有什么逻辑限制?
有很多原因你不只是拥有大量的寄存器:
- 他们与大多数pipe道阶段高度相关。 对于初学者,你需要跟踪他们的生命周期,并将结果转回到之前的阶段。 复杂性非常迅速地变得难以处理,并且涉及的线(数字)的数量以相同的速率增长。 面积很大,最终意味着在一定的时间点之后,在功率,价格和性能方面都是昂贵的。
- 它占用指令编码空间。 16个寄存器占用4位的源和目标,另外4个如果你有3个操作数的指令(如ARM)。 这是一个很大的指令集编码空间,只是为了指定寄存器。 这最终会影响解码,代码大小和复杂性。
- 有更好的方法来达到相同的结果…
这些天我们确实有很多寄存器 – 它们只是没有明确的编程。 我们有“注册重命名”。 虽然你只能访问一个小的集合(8-32寄存器),但实际上它们是由一个更大的集合(例如64-256)支持的。 CPU然后跟踪每个寄存器的可见性,并将它们分配给重命名的集合。 例如,您可以加载,修改,然后连续多次存储到某个寄存器,并根据caching未命中情况,实际上独立执行这些操作。在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的序列。 假设由于caching未命中而导致“r4”中指针的加载停止。 没关系 – 我们不需要等待“r0”准备就绪。 因为它被重命名,所以我们可以用“v1”(也映射到r0)来运行下一个序列 – 也许这是一个caching命中,我们只有一个巨大的性能胜利。
ldr v0, [v2] add v0, v0, #1 str v0, [v2] ldr v1, [v3] add v1, v1, #1 str v1, [v3]
我认为x86现在已经达到了大量的重命名寄存器(256场)。 这就意味着每个指令只需要8位乘以2来说明源和目标是什么。 这将大量增加整个核心所需的电线数量和尺寸。 所以大多数devise人员已经解决了16-32寄存器的问题,而对于无序的CPUdevise,寄存器重命名是缓解它的方法。
编辑 :乱序执行和寄存器重命名的重要性。 一旦你有了OOO,寄存器的数量就不重要了,因为它们只是“临时标签”,并且被重新命名为更大的虚拟寄存器组。 你不希望数字太小,因为编写小代码序列变得困难。 这对于x86-32来说是一个问题,因为有限的8个寄存器意味着很多临时对象会通过堆栈,而内核需要额外的逻辑来将读/写转发到内存。 如果你没有OOO,那么你通常会谈论一个小型核心,在这种情况下,一个大型的注册集合是一个很差的性价比的好处。
所以寄存器组的大小有一个天然的最佳位置,在大多数CPU类别中最多可以有32个寄存器。 x86-32有8个寄存器,它绝对太小了。 ARM去16个寄存器,这是一个很好的妥协。 如果有的话,32个寄存器有点太多 – 最终不需要最后10个寄存器。
这些都不涉及你获得SSE和其他vector浮点协处理器的额外寄存器。 这些作为一个额外的集合是有意义的,因为它们独立于整数核心运行,并且不会成倍增加CPU的复杂性。
我们有更多的他们
因为几乎每条指令都必须select1,2或3个体系结构可见的寄存器,所以扩展它们的数目会在每条指令上增加几位代码的大小,从而降低代码密度。 它还增加了必须保存为线程状态的上下文的数量,并部分保存在函数的激活logging中 。 这些操作经常发生。 pipe道互锁必须为每个寄存器检查记分板,这具有二次时间和空间的复杂性。 也许最大的原因是与已定义的指令集简单兼容。
但事实certificate,由于寄存器重命名 ,我们真的有很多寄存器可用,我们甚至不需要保存它们。 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的值不再被使用。 这允许第一次加载被延迟(即使片上高速caching命中通常需要几个周期),而不需要第二次加载或第二次存储的延迟。
它们一直添加寄存器,但是它们通常与专用指令(例如SIMD,SSE2等)绑定,或者需要编译到特定的CPU架构,这降低了可移植性。 现有的指令通常在特定的寄存器上工作,如果可用,则不能利用其他寄存器。 旧版指令集和全部。
要在这里添加一些有趣的信息,您将注意到具有8个相同大小的寄存器允许操作码保持与hex符号的一致性。 例如,指令push ax
是x86上的操作码0x50,最后一个寄存器di是0x57。 然后指令pop ax
从0x58开始,上升到0x5F pop di
来完成第一个基址-16。 每个大小有8个寄存器保持hex一致性。