Assembly 首次使用AVX 256位矢量会降低128位矢量和AVX标量运算的速度

Assembly 首次使用AVX 256位矢量会降低128位矢量和AVX标量运算的速度,assembly,x86-64,sse,simd,avx,Assembly,X86 64,Sse,Simd,Avx,最初,我试图重现Agner Fog的微体系结构指南部分“YMM和ZMM向量指令的预热期”中描述的效果,其中指出: 处理器在不使用时关闭向量执行单元的上部,以节省电源。在大约56000个时钟周期或14μs的初始预热期间,具有256位向量的指令的吞吐量大约是正常的4.5倍 我得到了减速,尽管它似乎更接近2倍,而不是4.5倍。但我发现,在我的CPU(Intel i7-9750H Coffee Lake)上,速度放缓不仅影响256位操作,还影响128位向量运算和标量浮点运算(以及XMM指令后N个仅GPR

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

处理器在不使用时关闭向量执行单元的上部,以节省电源。在大约56000个时钟周期或14μs的初始预热期间,具有256位向量的指令的吞吐量大约是正常的4.5倍

我得到了减速,尽管它似乎更接近2倍,而不是4.5倍。但我发现,在我的CPU(Intel i7-9750H Coffee Lake)上,速度放缓不仅影响256位操作,还影响128位向量运算和标量浮点运算(以及XMM指令后N个仅GPR指令)

基准程序代码:

# 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冷态(未执行256位/512位指令约675µs)遇到带有YMM(ZMM)目标寄存器的单个指令。CPU立即切换到某种“转换到AVX温暖”状态。这大概需要阿格纳指南中提到的约100-200个周期。这一“过渡”期持续约56000个周期

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

节流可能会限制分配给执行单元的µop的数量(不管这些µop是什么;只要至少有一个µop带有向量目标寄存器)


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



相关:“仅电压转换”部分可能描述了相同的效果。尽管作者在过渡期测量了YMM向量的性能,但得出的结论是,当在过渡期遇到向量寄存器接触指令时,被拆分的不是向量的上部,而是应用于整个管道的节流。(编辑:这篇博文没有测量过渡期内的XMM寄存器,这正是本文所测量的)。

即使对于窄SIMD指令,您也会看到节流,这是我称之为隐式加宽的行为的副作用

基本上,在现代Intel上,如果在
ymm0
ymm15
范围内的任何寄存器上,高位128-255位都是脏的,则任何SIMD指令都会在内部扩展到256位,因为高位需要归零,这需要寄存器文件中的完整256位寄存器通电,可能还需要256位ALU路径。因此,该指令用于AVX频率,就好像它是256位宽一样

类似地,如果在
zmm0
zmm15
范围内的任何zmm寄存器上,位256到511变脏,则操作会隐式扩展到512位

对于轻指令与重指令,加宽指令的类型与全宽指令相同。也就是说,将128位FMA扩展为512位的FMA充当“重AVX-512”,即使仅发生128位FMA

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


请注意,这不仅仅适用于此限制期:这意味着如果您的上限不干净,则窄SIMD指令(或标量FP)将导致转换到更保守的DVFS状态,就像全宽指令一样。

这可能是吗?你的标题提到了“SSE”标量运算,但我仍然只看到像
vaddss
,而不是
addss
这样的AVX编码。如果您确实有任何遗留SSE,那么请参阅may.And no,非站点链接不足以解决堆栈溢出问题。在问题本身中包含一个。(有一个关于更多细节和更多变体的非现场链接是很好的,但至少代码的基本部分,可能减去一些样板文件,应该是问题所在。就像你在这里看到的,但其中一个变体的重要部分没有注释。)@PeterCordes完成了,我现在只包含了要点的主体,而不是伪代码。如果足够清楚,请告诉我。@Noah“电压转换期间,所有指令都会受到IPC降低的影响”我想您可能遗漏了一个重要的细节。并非所有指令都受电压转换本身的影响。GPR代码将在转换期间正常运行。但若在转换过程中看到向量接触指令,并且CPU处于脏上限状态,那个么它将在N个周期内开始节流。节流会影响所有指令是的。我只是很惊讶,即使是128位AVX指令也会导致限制,这似乎没有提到。正确:如果指令窗口中有任何“宽”指令,则会发生限制,并且在发生限制时会影响所有指令(SIMD或其他)。令人困惑的是,“宽”指令在大多数情况下都包含窄SIMD(参见答案)。这是一个很好的解释,谢谢!最后一点也很有启发性,我想这是使用zeroupper的一个很好的理由,即使只使用AVX代码。