assembly - 首次使用 AVX 256 位向量会减慢 128 位向量和 AVX 标量操作

标签 assembly x86-64 sse simd avx

最初我试图重现 Agner Fog 的微体系结构指南部分“YMM 和 ZMM 向量指令的预热期”中描述的效果,它说:

The processor turns off the upper parts of the vector execution units when it is not used, in order to save power. Instructions with 256-bit vectors have a throughput that is approximately 4.5 times slower than normal during an initial warm-up period of approximately 56,000 clock cycles or 14 μs.

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

基准程序代码:

# Compile and run:
# clang++ ymm-throttle.S && ./a.out

.intel_syntax noprefix

.data
L_F0:
  .asciz "ref cycles = %u\n"

.p2align 5
L_C0:
  .long 1
  .long 2
  .long 3
  .long 4
  .long 1
  .long 2
  .long 3
  .long 4

.text

.set initial_scalar_warmup, 5*1000*1000
.set iteration_count, 30*1000
.set wait_count, 50*1000

.global _main
_main:
  # ---------- Initial warm-up
  # It seems that we enter _main (at least in MacOS 11.2.2) in a "ymm warmed-up" state.
  #
  # Initial warm-up loop below is long enough for the processor to switch back to
  # "ymm cold" state. It also may reduce dynamic-frequency scaling related measurements
  # deviations (hopefully CPU is in full boost by the time we finish initial warmup loop).

  vzeroupper

  push rbp
  mov ecx, initial_scalar_warmup

.p2align 4
_initial_loop:
  add eax, 1
  add edi, 1
  add edx, 1

  dec ecx
  jnz _initial_loop

  # --------- Measure XMM

  # TOUCH YMM.
  # Test to see effect of touching unrelated YMM register
  # on XMM performance.
  # If "vpxor ymm9" below is commented out, then the xmm_loop below
  # runs a lot faster (~2x faster).
  vpxor ymm9, ymm9, ymm9

  mov ecx, iteration_count
  rdtsc
  mov esi, eax

  vpxor xmm0, xmm0, xmm0
  vpxor xmm1, xmm1, xmm1
  vpxor xmm2, xmm2, xmm2
  vmovdqa xmm3, [rip + L_C0]

.p2align 5
_xmm_loop:
  # Here we only do XMM (128-bit) VEX-encoded op. But it is triggering execution throttling.
  vpaddd xmm0, xmm3, xmm3
  add edi, 1
  add eax, 1

  dec ecx
  jnz _xmm_loop

  lfence
  rdtsc
  sub eax, esi
  mov esi, eax  # ESI = ref cycles count

  # ------------- Print results

  lea rdi, [rip + L_F0]
  xor eax, eax
  call _printf

  vzeroupper
  xor eax, eax
  pop rbp
  ret​

问题:我的基准测试是否正确?对正在发生的事情的描述(如下)是否合理?

CPU 处于 AVX-cold 状态(在 ~675 µs 内未执行任何 256 位/512 位指令)遇到一 strip 有 YMM (ZMM) 目标寄存器的指令。 CPU 立即切换到某种“过渡到 AVX-warm”状态。这大概需要 Agner 指南中提到的 ~100-200 个周期。这个“过渡”期持续约 56,000 个周期。

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

也许节流限制了分配给执行单元的微操作数(不管这些微操作是什么;只要至少有一个微操作带有向量目标寄存器)?


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


相关:an article at Travis Downs blog 的“仅电压转换”部分可能在描述相同的效果。虽然作者在过渡期间测量了 YMM 向量的性能,但结论是它不是向量的上部被拆分,而是在过渡期间遇到向量寄存器触摸指令时对整个流水线进行节流。 (编辑:博客文章在过渡期间没有测量 XMM 寄存器,这是本文测量的内容)。

最佳答案

即使对于窄 SIMD 指令,您也会看到节流这一事实是我称之为隐式扩大的行为的副作用。

基本上,在现代 Intel 上,如果 任何 寄存器的高 128-255 位是脏的,范围从 ymm0ymm15,< em>any SIMD 指令在内部扩展到 256 位,因为高位需要清零,这需要寄存器文件中的完整 256 位寄存器供电,可能还需要 256 位 ALU 路径.因此,为了 AVX 频率的目的,该指令就好像它是 256 位宽的一样。

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

为了轻型和重型指令的目的,加宽指令与全宽指令具有相同的类型。也就是说,即使只有 128 位 FMA 发生,128 位 FMA 被扩展到 512 位时仍充当“重 AVX-512”。

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

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

关于assembly - 首次使用 AVX 256 位向量会减慢 128 位向量和 AVX 标量操作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66874161/

相关文章:

assembly - 使用较小的寄存器有什么好处,例如al 与 eax/rax

assembly - 64 位模式不支持 32 位 PUSH 和 POP 指令

c++ - 使用 pre-SSE4 计算 vector2 double 的 floor & ceil

c++ - 定义寄存器堆栈数组

clang - 为什么添加 xorps 指令使这个函数使用 cvtsi2ss 并添加 ~5x 快?

assembly - 当 CPU 处于内核态时,它可以读写任何寄存器吗?

c++ - 无法使 rustc 使用 simd 指令进行包含范围循环

c - NASM 中的变量

assembly - 通过寄存器重命名器对寄存器进行微架构清零 : performance versus a mov?

c++ - 将汇编代码移植到 C++