【问题标题】:Is there any situation where using MOVDQU and MOVUPD is better than MOVUPS?有没有使用 MOVDQU 和 MOVUPD 比 MOVUPS 更好的情况?
【发布时间】:2025-12-23 13:40:15
【问题描述】:

我试图了解英特尔 x86-64 上 SSE 的不同 MOV 指令。

根据this,在 2 个寄存器之间移动数据时,您应该使用对齐指令(MOVAPS、MOVAPD 和 MOVDQA),并为您正在使用的类型使用正确的指令。并且在将寄存器移动到内存时使用 MOVUPS/MOVAPS,反之亦然,因为类型在移入/移出内存时不会影响性能。

那么有任何理由使用 MOVDQU 和 MOVUPD 吗?我在链接上的解释有误吗?

【问题讨论】:

  • 认为这可能对某些 CPU 上的负载使用延迟很重要,但我没有测试过/不记得我读到了什么(我可能稍后再回答)。 MOVUPD 总是无用的,因为没有 CPU 关心双浮点数和单浮点数,但是当使用 MOVUPS 负载的结果作为整数向量指令的输入时,有些可能会有额外的旁路延迟。如果您查看编译器输出,一些编译器总是使用 MOVU/APS 进行存储,但仍然使用匹配类型进行加载。
  • 类型在从/向内存移动时不会影响性能,但如果您使用movups 加载一个值,然后对其执行整数运算,则会受到惩罚。这就是存在整数类型和浮点类型移动指令的原因。
  • 所以如果我用movdqu从内存中加载一些东西到xmm1,然后用xmm1做一个浮点运算,会有惩罚吗?
  • @DamianPereira 没错。这就是为什么您应该始终使用适合类型的mov 指令。
  • 请注意,您引用的有关 SSE 移动性能的链接相当陈旧,可能仅适用于老一代

标签: assembly x86 x86-64 intel sse


【解决方案1】:

总结:我不知道最近有任何 x86 架构在使用“错误”加载指令(即加载指令后跟来自相反域的 ALU 指令)时会导致额外延迟.

这是Agner has to say 关于绕过延迟的内容,这些延迟是您在 CPU 中的各个执行域之间移动时可能遇到的延迟(有时这些是不可避免的,但有时它们可​​能是由于使用此处有争议的指令的“错误”版本):

Nehalem 上的数据绕过延迟 在 Nehalem 上,执行单元是 分为五个“域”:

整数域处理通用的所有操作 寄存器。整数向量 (SIMD) 域处理整数运算 在向量寄存器中。 FP域处理浮点运算 在 XMM 和 x87 寄存器中。加载域处理所有内存读取。 存储域处理所有内存存储。有额外的延迟 1 或 2 个时钟周期,当在一个域中的操作的输出 在另一个域中用作输入。这些所谓的旁路延迟是 见表 8.2。

使用加载和存储仍然没有额外的绕过延迟 关于错误数据类型的说明。例如,它可以是 方便在整数数据上使用 MOVHPS 来读取或写入 XMM 寄存器的上半部分。

最后一段的重点是我的,也是关键部分:旁路延迟不适用于 Nehalem 加载和存储指令。直观地说,这是有道理的:加载和存储单元专用于整个内核,并且必须以适合任何执行单元(或将其存储在 PRF 中)的方式提供它们的结果 - 与 ALU 的情况不同,同样关注转发不存在。

现在不再关心 Nehalem,但在 Sandy Bridge/Ivy Bridge、Haswell 和 Skylake 的部分中,您会发现这些域与 Nehalem 的讨论内容相同,并且总体延迟更少.因此,可以假设加载和存储不会因指令类型而受到延迟的行为仍然存在。

我们也可以测试它。我写了一个这样的基准:

bypass_movdqa_latency:
    sub     rsp, 120
    xor     eax, eax
    pxor    xmm1, xmm1
.top:
    movdqa  xmm0, [rsp + rax] ; 7 cycles
    pand    xmm0, xmm1        ; 1 cycle
    movq    rax, xmm0         ; 1 cycle
    dec     rdi
    jnz     .top
    add     rsp, 120
    ret

这会使用movdqa 加载一个值,对其进行整数域运算(pand),然后将其移动到通用寄存器rax,以便它可以用作movdqa 地址的一部分在下一个循环中。我还创建了 3 个与上述相同的其他基准测试,除了将 movdqa 替换为 movdqumovupsmovupd

Skylake-client 上的结果(i7-6700HQ 和最近的微码):

** Running benchmark group Vector unit bypass latency **
                     Benchmark   Cycles
  movdqa [mem] -> pxor latency     9.00
  movdqu [mem] -> pxor latency     9.00
  movups [mem] -> pxor latency     9.00
  movupd [mem] -> pxor latency     9.00

在每种情况下,往返延迟都是相同的:9 个周期,正如预期的那样:负载为 6 + 1 + 2 个周期,分别为 pxormovq

所有这些测试都添加到uarch-bench 中,以防您想在任何其他架构上运行它们(我会对结果感兴趣)。我用的是命令行:

./uarch-bench.sh --test-name=vector/* --timer=libpfc

【讨论】:

  • paddd 或其他特定的整数指令可能是比pxor 更好的选择。在 Skylake 上,整数和 FP 向量布尔值基本相同,因此这些执行单元可能无论如何都连接到两个转发网络。 (当在 FP 指令之间使用时,延迟取决于指令碰巧选择的端口。)
  • 此外,SKL 绕过延迟很奇怪:即使在 PRF 中很冷,如何设置寄存器值也很重要,并且会增加 other 操作数的延迟。例如如果 xmm1 来自整数,addps xmm0, xmm1 从 xmm0->xmm0 具有更高的延迟。 IDK 为什么在任何“绕过”完成后很长时间跨域仍然很重要。
  • 我想 SIMD 单元中的整个整数域与浮点域的关系将在几代后消失。它们都使用相同的执行单元。而且我认为没有必要拆分寄存器文件。如果有的话,我们可能会开始看到基于端口的旁路延迟。 (我们已经在 Skylake X 上的 port5 FMA 中看到了这一点。)
  • @PeterCordes - 我认为“二进制操作”执行单元更有可能同时出现在 FP 和 Integer 方面 - 毕竟,这些操作或多或少是微不足道的,可能只是其他电路的一小部分。这似乎比以某种方式避免域间转发延迟的特殊“中间”单元更可行。我使用了pandb(不是pxor - 描述错误),因为它可以方便地将基准中从内存加载的值归零(避免部分循环需要存储转发)。
  • 无论如何,我现在changed the test 使用paddb(Skylake 上的结果相同)。
【解决方案2】:

请注意,您引用的有关 SSE 移动性能的链接相当陈旧,可能仅适用于老一代英特尔硬件。我了解到最近的微架构提高了例如未对齐的加载指令,如果它们用于实际对齐的数据。总而言之,简短的基准测试是适用于您拥有的特定硬件的有效信息的最佳来源。

【讨论】:

  • 是的,对齐数据上的未对齐负载现在确实没有额外成本。但问题是在使用paddd 之类的指令之前,是否存在使用movups(比movdqu 短1 个字节)加载整数数据的域交叉vec-fp 到vec-int 惩罚(如延迟)。可能是为了加载,但 AFAIK 对商店没有惩罚。或者至少有些编译器选择使用movups 存储,而不管创建向量的指令是什么。
最近更新 更多