内存从处理器获取

时间:2018-02-05 00:55:29

标签: assembly memory-management cpu-architecture

假设我给出了以下汇编代码:

Mul b,b,b

这意味着首先计算 b 的平方,然后存储在 b 中。我的问题是: 当处理器试图从b(b的值)中获取内存时,如果它尝试获取同一个变量,那么会尝试两次还是只尝试一次?

1 个答案:

答案 0 :(得分:4)

3操作数内存到内存的机器只存在于理论计算机科学AFAIK中。这些天每个人都建立寄存器机器,或(在低端微控制器中)累加器机器(通常带有一个或两个指针寄存器以及实际的累加器),因为它具有寄存器比需要存储器更有效(或缓存)为计算链中的每一步存储/重新加载往返。

然而,是的,当多个源操作数对同一地址进行编码时,通过仅执行一次高速缓存读取来设计CPU以进行优化是可能的(也是一个好主意)。

  

我需要找到以字节为单位的程序大小。所以我只是想知道b的值是否会被访问两次?

这两件事是无关的。机器代码仍然需要对b进行两次编码,除非有一个特殊的" square"只有一个源操作数空间的指令。在这种情况下,你肯定希望它只被访问一次。 (它可能没有单独的助记符,只是汇编程序在两个源操作数相同时可以使用的mul的不同操作码。)

或许机器编码允许第二个源操作数显式引用第一个源操作数,而不必再次独立指定b的地址。但是,CPU可以将b, same_as_first解码为b, b,然后再读取b两次。即,仅在解码器中处理该特殊情况,而不是在该情况下在操作数读取阶段中提供优化路径。花费额外的晶体管来实现优化可能是值得的,但你不能假设任何东西。 (即使在这种特殊情况下,指令编码对第二个操作数有一个"同上"编码。)而且顺便说一下,我完全是这样做的。我还没有听说过像这样的真正的ISA。 VAX对两个操作数都有完全灵活的编码,两者都可以是内存,但是AFAIK它们不能相互引用。

英特尔P6系列确实对寄存器读取(而不是内存读取)进行了此优化,这很重要,因为它的永久/退出寄存器文件的读取端口有限。

x86是一种寄存器架构,主要是2操作数指令。大多数指令支持内存源或内存目标(但不能同时支持两个指令)。但是没关系,这里有趣的类比是P6如何处理读取寄存器源操作数,类似于您对3操作数内存到内存架构中源操作数的想法。

英特尔P6微体系结构是一种带有寄存器重命名的3宽无序设计。大多数"简单" x86指令解码为单个内部uop,它实际上是在无序核心中重命名和跟踪的。 (Pentium Pro / Pentium II是最初的P6微架构.P6系列的后来成员,Pentium III和Pentium M是3宽,而Core2和Nehalem是4宽。)

Sandybridge is a new microarchitecture family切换到使用物理寄存器文件,不再有寄存器读取停顿。

P6-family有一个永久寄存器文件,用于保存架构寄存器的退出状态。但是无序机器将寄存器输入值保存在ReOrder缓冲区中。 (与具有物理寄存器文件的设计不同,其中ROB具有指针到PRF条目,而不是直接的值)。

如果输入到uop的寄存器来自尚未退休的uop,则该值仍然是" live"在ROB中。这是正常情况:大多数代码使用新值重复重写相同的寄存器,特别是因为32位x86只有8个整数寄存器。大多数x86指令都是2操作数,带有读/写目的地,如add edx, ecx。 (edx += ecx)。

但是当重命名一组具有来自最近没有编写的寄存器的输入的uop(即写入该寄存器的uop已经退役)时,ROB读取阶段(在重命名阶段之后)必须阅读所有需要的"冷"从永久寄存器文件中将值注册到ROB中。

See Agner Fog's microarch PDF,章节:Pentium Pro / PII / PIII管道, 6.5 ROB read 部分了解更多详情。 在这些第一代P6 CPU中,永久寄存器文件只有2个读取端口,但3个uop,最多2个输入,每个最多可读取6个寄存器。如果它们全部冷,那么ROB读取阶段将为该问题组花费3个总循环。 但如果读取相同的冷寄存器6次,则没有问题:硬件通知重叠,只读一次。

更多示例:lea rax, [rdx + rcx*4]如果rdxrcx最近没有编写,则会消耗2个读取端口(因此值仍然不在 - 在ReOrder缓冲区中飞行)。但是lea rax, [rdx + rdx*4]只会消耗1个端口。

我使用LEA作为一个示例,与单独的只写目标更像RISC。但是(寄存器读取停顿的)性能问题是相同的:add必须读取两个源寄存器。

在3或4 uop的同一组中重命名/发布的其他指令(实际上是uops)也可以共享读端口,如果它们中的任何一个读取相同的"冷"寄存器。例如在同一组中重命名的add eax, esi / add edx, esi只需阅读esi一次。 (eax对于第一个add也可能是冷的,但第二个add从第一个eax开始作为其输入。显然,ROB读取阶段无法读取该值,因此它只标记第一个add uop将其结果写入第二个add的输入字段,或类似的内容。)

当然,写eax使它成为现场"在重新排序缓冲区中,直到指令退出,这就是为什么P6可以正常运行,即使只有几个读端口用于最近写入的寄存器。 P6是在x86-64存在之前设计的(Core2是第一个支持64位的P6成员,而Nehalem引入了更多的寄存器读取带宽)。在x86-64中有更多的寄存器可以在寄存器中保留更多的常量,因此你更有可能读取最近没有写过的寄存器。

Sandybridge切换到物理寄存器文件,这允许ROB增长,因为每个条目都更加紧凑:不需要每个值的副本作为每个uop的输入,多个uop读取相同的寄存器指向相同的PRF入场。 Sandybridge还增加了AVX,它将向量寄存器扩展到256位。在每个uop条目中有两个256b输入的空间会非常疯狂。