【问题标题】:Is mov r64, m64 one cycle or two cycle latency?mov r64, m64 是一个周期还是两个周期的延迟?
【发布时间】:2019-06-02 00:35:39
【问题描述】:

我在 IvyBridge,我写了以下简单的程序来测量mov 的延迟:

section .bss
align   64
buf:    resb    64

section .text
global _start
_start:
    mov rcx,    1000000000
    xor rax,    rax
loop:
    mov rax,    [buf+rax]

    dec rcx,
    jne loop

    xor rdi,    rdi
    mov rax,    60
    syscall

perf 显示结果:

 5,181,691,439      cycles

所以每次迭代都有 5 个周期延迟。我从多个网上资源搜索,L1缓存的延迟是4。因此mov本身的延迟应该是1。

但是,Agner 指令表显示 mov r64, m64 对 IveBridge 有 2 个周期延迟。我不知道其他地方可以找到这种延迟。

我在上面的测量程序中犯了错误吗?为什么这个程序显示mov 的延迟是 1 而不是 2?

(我通过使用 L2 缓存得到了相同的结果:如果 buf+rax 是 L1 缺少 L2 命中,类似的测量显示 mov rax, [buf+rax] 有 12 个周期延迟。IvyBridge 有 11 个周期延迟 L2 缓存,所以 mov 延迟是还是1个周期)

【问题讨论】:

    标签: assembly x86 cpu-cache microbenchmark micro-architecture


    【解决方案1】:

    因此 mov 本身的延迟应该是 1。

    不,mov 负载。数据也不需要经过 ALU mov 操作。


    Agner Fog 的指令表不包含加载使用延迟(就像您正在测量的那样)。它们在他的 microarch PDF 中的“缓存和内存访问”部分的表中每个 uarch。例如SnB/IvB(第 9.13 节)有一个“1 级数据”行,其中包含“32 kB,8 路,64 B 行大小,延迟 4,每个内核”。

    这 4 个周期的延迟是像 mov rax, [rax] 这样的依赖指令链的加载使用延迟。 您正在测量 5 个周期,因为您使用的是 [reg + 0..2047] 以外的寻址模式。 对于小的位移,加载单元推测直接使用基址寄存器作为 TLB 查找的输入将给出结果与使用加法器结果相同。 Is there a penalty when base+offset is in a different page than the base?。因此,您的寻址模式[disp32 + rax] 使用正常路径,在加载端口中开始 TLB 查找之前,再等待一个周期等待加法器结果。


    对于不同域之间的大多数操作(如整数寄存器和 XMM 寄存器),您只能真正测量像 movd xmm0,eax / mov eax, xmm0 这样的往返,并且很难将其分开并弄清楚延迟是多少每条指令分别1.

    对于加载,您可以链接到另一个加载来测量缓存加载使用延迟,而不是存储/重新加载链。

    Agner 出于某种原因决定查看他的表的存储转发延迟,并完全任意选择如何在存储之间拆分存储转发延迟和重新加载。

    (来自他的指令表电子表格的“术语定义”表,在介绍之后的左侧)

    无法测量内存读取或写入的延迟 用软件方法进行教学。只能测量 内存写入的组合延迟,然后是从内存读取 同一个地址。 这里测量的实际上并不是缓存访问 时间, 因为在大多数情况下,微处理器足够聪明,可以使 直接从写入单元到读取单元的“存储转发” 而不是等待数据进入缓存并再次返回。 这个存储转发过程的延迟是任意划分的 表中的写入延迟和读取延迟。但实际上, 对性能优化有意义的唯一值是总和 写入时间和读取时间。

    这显然是不正确的:L1d 加载使用延迟是指针追逐通过间接级别的事情。您可能会争辩说它只是可变的,因为某些负载可能会在缓存中丢失,但是如果您要选择要放入表中的内容,则最好选择 L1d 负载使用延迟。然后计算存储延迟数,使得存储+加载延迟=存储转发延迟,就像现在一样。英特尔凌动的存储延迟 = -2,因为它具有 3c L1d load-use latency,但根据 Agner 的 uarch 指南进行 1c 存储转发。

    例如,这对于加载到 XMM 或 YMM 寄存器来说不太容易,但在计算出 movq rax, xmm0 的延迟后仍然可能。 x87 寄存器更难,因为无法通过 ALU 直接将数据从 st0 获取到 eax/rax,而不是通过存储/重新加载。但也许您可以使用像 fucomi 这样直接设置整数 FLAGS 的 FP 比较来做一些事情(在具有它的 CPU 上:P6 及更高版本)。

    不过,至少整数加载延迟来反映指针追逐延迟会好得多。 IDK 如果有人提议为他更新 Agner 的表格,或者他是否愿意接受这样的更新。不过,需要对大多数 uarch 进行新的测试,以确保您对不同的寄存器集具有正确的加载使用延迟。


    脚注 1:例如,http://instlatx64.atw.hu 不会尝试,只是在延迟列中显示“diff.reg.set”,而有用数据仅在吞吐量列中。但是他们有 MOVD r64, xmm+MOVD xmm, r64 往返线路,in this case 在 IvB 上总共有 2 个周期,所以我们可以非常确信它们单程只有 1c。不是零一种方式。 :P

    但是对于整数寄存器的加载,它们确实显示了 IvB 的 MOV r32, [m32] 的 4 周期加载使用延迟,因为显然它们使用 [reg + 0..2047] 寻址模式进行测试。

    https://uops.info/ 相当不错,但在延迟方面给出了相当宽松的界限:IIRC,它们构造了一个往返循环(例如存储和重新加载,或 xmm->integer 和 integer-> xmm),然后给出延迟的上限,假设每隔一个步骤只有 1 个周期。请参阅What do multiple values or ranges means as the latency for a single instruction? 了解更多信息。


    缓存延迟信息的其他来源:

    https://www.7-cpu.com/ 为许多其他 uarch 提供了很好的详细信息,甚至包括许多非 x86,如 ARM、MIPS、PowerPC 和 IA-64。

    这些页面还有其他详细信息,例如缓存和 TLB 大小、TLB 时间、分支未命中实验结果和内存带宽。缓存延迟详细信息如下所示:

    (from their Skylake page)

    • L1 数据缓存延迟 = 4 个周期,用于通过指针进行简单访问
    • L1 数据缓存延迟 = 5 个周期,用于复杂地址计算的访问 (size_t n, *p; n = p[n])。
    • L2 缓存延迟 = 12 个周期
    • L3 缓存延迟 = 42 个周期(核心 0)(i7-6700 Skylake 4.0 GHz)
    • L3 缓存延迟 = 38 个周期(i7-7700K 4 GHz,Kaby Lake)
    • RAM 延迟 = 42 个周期 + 51 ns (i7-6700 Skylake)

    【讨论】:

    • 非常感谢!找了好久,原来我对数据有一些误解。不过我仍然有些疑问:我的buf+rax 是在哪里计算的?我认为它在 AGU 而不是 ALU 上,所以当它像 buf+rax 时,AGU 将首先计算结果(1 个周期),然后从 L1d 缓存加载数据(4 个周期)。因为我在网上找到了一些图,显示端口 2 和 3 的 AGU 连接到 L1d 缓存,它们负责加载数据。我说的对吗?
    • @user10865622: 是的,它是在 AGU 内部根据 p2 或 p3 中的哪一个被调度来计算的。这就是 AGU 的用途。
    • 关于阿格纳的指令表,他是如何计算写入的数据的信息在哪里?我找不到“只看他的表的存储转发延迟”,我只能找到那些数据,没有任何其他信息。
    • @user10865622:查看指令表电子表格中的“术语定义”表,位于最左侧。那里埋着一些介绍性的东西。用相关段落的引用更新了我的答案。
    猜你喜欢
    • 1970-01-01
    • 2019-06-14
    • 2012-06-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-05
    相关资源
    最近更新 更多