首次使用 AVX 256 位向量会减慢 128 位向量和 AVX 标量运算

ste*_*pan 4 assembly sse x86-64 simd avx

最初,我试图重现 Agner Fog 的微架构指南部分“YMM 和 ZMM 向量指令的预热期”中描述的效果,其中写道:

\n
\n

处理器在不使用时关闭向量执行单元的上部部分,以节省电力。在大约 56,000 个时钟周期或 14 \xce\xbcs 的初始预热期间,具有 256 位向量的指令的吞吐量大约比正常情况慢 4.5 倍。

\n
\n

我得到了减速,尽管看起来更接近 2 倍而不是 4.5 倍。但我发现在我的 CPU(Intel i7-9750H Coffee Lake)上,速度下降不仅影响 256 位操作,还影响 128 位向量操作和标量浮点操作(甚至 N 个 GPR-仅限 XMM 触摸指令之后的指令)。

\n

基准程序代码:

\n
# Compile and run:\n# clang++ ymm-throttle.S && ./a.out\n\n.intel_syntax noprefix\n\n.data\nL_F0:\n  .asciz "ref cycles = %u\\n"\n\n.p2align 5\nL_C0:\n  .long 1\n  .long 2\n  .long 3\n  .long 4\n  .long 1\n  .long 2\n  .long 3\n  .long 4\n\n.text\n\n.set initial_scalar_warmup, 5*1000*1000\n.set iteration_count, 30*1000\n.set wait_count, 50*1000\n\n.global _main\n_main:\n  # ---------- Initial warm-up\n  # It seems that we enter _main (at least in MacOS 11.2.2) in a "ymm warmed-up" state.\n  #\n  # Initial warm-up loop below is long enough for the processor to switch back to\n  # "ymm cold" state. It also may reduce dynamic-frequency scaling related measurements\n  # deviations (hopefully CPU is in full boost by the time we finish initial warmup loop).\n\n  vzeroupper\n\n  push rbp\n  mov ecx, initial_scalar_warmup\n\n.p2align 4\n_initial_loop:\n  add eax, 1\n  add edi, 1\n  add edx, 1\n\n  dec ecx\n  jnz _initial_loop\n\n  # --------- Measure XMM\n\n  # TOUCH YMM.\n  # Test to see effect of touching unrelated YMM register\n  # on XMM performance.\n  # If "vpxor ymm9" below is commented out, then the xmm_loop below\n  # runs a lot faster (~2x faster).\n  vpxor ymm9, ymm9, ymm9\n\n  mov ecx, iteration_count\n  rdtsc\n  mov esi, eax\n\n  vpxor xmm0, xmm0, xmm0\n  vpxor xmm1, xmm1, xmm1\n  vpxor xmm2, xmm2, xmm2\n  vmovdqa xmm3, [rip + L_C0]\n\n.p2align 5\n_xmm_loop:\n  # Here we only do XMM (128-bit) VEX-encoded op. But it is triggering execution throttling.\n  vpaddd xmm0, xmm3, xmm3\n  add edi, 1\n  add eax, 1\n\n  dec ecx\n  jnz _xmm_loop\n\n  lfence\n  rdtsc\n  sub eax, esi\n  mov esi, eax  # ESI = ref cycles count\n\n  # ------------- Print results\n\n  lea rdi, [rip + L_F0]\n  xor eax, eax\n  call _printf\n\n  vzeroupper\n  xor eax, eax\n  pop rbp\n  ret\xe2\x80\x8b\n
Run Code Online (Sandbox Code Playgroud)\n

问题:我的基准正确吗?对正在发生的事情的描述(如下)看起来合理吗?

\n

CPU 处于 AVX 冷状态(约 675 \xc2\xb5s 内未执行 256 位/512 位指令)遇到带有 YMM (ZMM) 目标寄存器的单个指令。CPU 立即切换到某种“过渡到 AVX-warm”状态。这大概需要 Agner 指南中提到的大约 100-200 个周期。这个“过渡”期持续约 56\'000 个周期。

\n

在过渡期间,GPR 代码可以正常执行,但任何具有向量目标寄存器的指令(包括 128 位 XMM 或标量浮点指令,甚至包括vmovq xmm0, rax)都会对整个执行管道进行限制。这会影响紧随此类指令的 N 个周期的仅 GPR 代码(不确定有多少;可能相当于指令的十几个周期)。

\n

也许限制会限制调度到执行单元的 \xc2\xb5ops 数量(无论这些 \xc2\xb5ops 是什么;只要至少有一个带有向量目标寄存器的 \xc2\xb5ops)?

\n
\n

对我来说,这里的新内容是,我认为在过渡期间限制将仅适用于 256 位(和 512 位)指令,但似乎任何具有向量寄存器目标的指令都会受到影响(以及~20-60 探地雷达 - 仅立即遵循说明;无法在我的系统上进行更精确的测量)。

\n
\n

相关:Travis Downs 博客上一篇文章的“仅电压转换”部分可能描述了相同的效果。尽管作者在过渡期间测量了 YMM 向量的性能,但得出的结论是,并不是向量的上半部分被分割,而是在过渡期间遇到向量寄存器接触指令时对整个管道进行了限制。(编辑:博客文章没有在过渡期间测量 XMM 寄存器,而这正是本文所测量的)。

\n

Bee*_*ope 6

事实上,即使对于窄 SIMD 指令,您也会看到限制,这是我称为“隐式加宽”的行为的副作用。

基本上,在现代 Intel 上,如果范围内的任何寄存器的高128-255位是脏的,任何SIMD 指令都会在内部扩展为 256 位,因为高位需要清零,这需要完整的 256 位寄存器在要供电的寄存器文件中,也可能在 256 位 ALU 路径中。因此,该指令对于 AVX 频率而言就像 256 位宽一样。ymm0ymm15

类似地,如果 到范围内的任何 zmm 寄存器上位 256 到 511 是脏的,则操作将隐式扩展到 512 位。zmm0zmm15

出于轻指令与重指令的目的,加宽指令的类型与全宽指令的类型相同。也就是说,扩展至 512 位的 128 位 FMA 充当“重型 AVX-512”,即使仅出现 128 位 FMA。

这适用于使用 xmm/ymm 寄存器的所有指令,甚至是标量 FP 操作。

请注意,这不仅仅适用于此节流周期:这意味着如果上层脏了,窄 SIMD 指令(或标量 FP)将导致转换到更保守的 DVFS 状态,就像全角指令一样。

  • 这是一个很好的解释,谢谢!最后一条注释也非常有启发性,我想这是使用 Zeroupper 的一个很好的理由,即使对于仅 AVX 的代码也是如此。 (2认同)