【问题标题】:Memory Fetching from processor从处理器获取内存
【发布时间】:2018-07-14 20:02:42
【问题描述】:

假设我得到以下汇编代码:

Mul b,b,b

表示b的平方先计算后存入b。我的问题是: 当处理器尝试从 b(b 的值)获取内存时,会尝试两次还是只尝试一次,因为它正在尝试获取相同的变量?

【问题讨论】:

  • 这完全取决于所涉及的微处理器的设计。请注意,微处理器具有 3 内存操作数指令的情况极为罕见,而且可能闻所未闻。
  • 三操作数内存到内存机器仅存在于理论计算机科学 AFAIK 中。现在每个人都在构建寄存器机器,或者至少是累加器机器(通常有一个或两个指针寄存器以及实际的累加器),因为拥有寄存器比需要内存(或缓存)存储/重新加载往返更有效计算链中的每一步。
  • @PeterCordes 感谢您提供的信息。另外,你能给我一些关于这个问题的见解吗? b 的值会被访问两次还是只访问一次还是取决于其他情况?
  • @IwillnotexistIdonotexist 我正在处理一个家庭作业问题。我需要以字节为单位找到程序大小。所以我只是想知道 b 的值是否会被访问两次?

标签: assembly memory-management cpu-architecture


【解决方案1】:

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

但是,是的,当多个源操作数编码相同的地址时,通过只进行一次缓存读取来设计一个 CPU 来优化是可能的(也是一个好主意)。

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

这两件事是无关的。机器代码仍然必须对b 进行两次编码,除非有一条特殊的“方形”指令只能容纳一个源操作数。在这种情况下,您肯定希望它只被访问一次。 (它可能没有单独的助记符,只是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 系列有一个永久寄存器文件,用于保存架构寄存器的停用状态。但是乱序机制将寄存器输入值保存在 ReOrder Buffer 中。 (与具有物理寄存器文件的设计不同,其中 ROB 具有指向 PRF 条目的指针,而不是直接的值)。

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

但是,当重命名一组输入来自最近未写入的寄存器的微指令时(即写入该寄存器的微指令已退休),ROB 读取阶段(重命名阶段之后)必须读取所有需要从永久寄存器文件中将“冷”寄存器值放入 ROB。

See Agner Fog's microarch PDF,章节:Pentium Pro / PII / PIII 管道,6.5 ROB 阅读部分了解更多详情。 在这些第一代 P6 CPU 中,永久寄存器文件只有 2 个读取端口,但 3 个 uop,每个最多 2 个输入,总共可以读取多达 6 个寄存器。如果它们都是冷的,ROB 读取阶段将需要 3 个周期来处理该问题组。 但是如果同一个冷寄存器被读取6次,就没有问题:硬件通知重叠,只读取一次。

更多示例:如果最近没有写入 rdxrcxlea rax, [rdx + rcx*4] 将消耗 2 个读取端口(因此这些值在 ReOrder 缓冲区中还没有进行中)。但是lea rax, [rdx + rdx*4] 只会消耗 1 个端口。

我以 LEA 为例,使其更像 RISC,具有单独的只写目标。但无论哪种方式,性能问题(寄存器读取停顿)都是相同的:add 必须读取两个源寄存器。

如果它们中的任何一个读取相同的“冷”寄存器,则在同一组 3 或 4 个微指令中重命名/发出的其他指令(实际上是微指令)也可以共享读取端口。例如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 输入留出空间会非常疯狂。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-05-24
    • 2018-09-22
    • 2013-09-15
    • 1970-01-01
    • 2014-05-05
    • 2020-06-18
    • 2011-08-05
    • 1970-01-01
    相关资源
    最近更新 更多