4

我在 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 指令表显示 IveBridgemov r64, m64有 2 个周期延迟。我不知道其他地方可以找到这种延迟。

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

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

4

1 回答 1

4

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

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


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 查找的输入将得到与使用加法器结果相同的结果。 当 base+offset 位于与 base 不同的页面时,是否会受到惩罚?. 因此,您的寻址模式[disp32 + rax]使用正常路径,在加载端口中开始 TLB 查找之前再等待一个周期等待加法器结果。


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

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

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

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

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

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

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

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


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

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

https://uops.info/非常好,但在延迟方面给出了相当宽松的界限:IIRC,它们构造了一个往返循环(例如存储和重新加载,或 xmm->integer 和 integer->xmm),然后假设每隔一个步骤只有 1 个周期,给出延迟的上限。请参阅多个值或范围作为单个指令的延迟意味着什么?更多。


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

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

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

来自他们的 Skylake 页面

  • 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)
于 2019-01-07T11:14:22.740 回答