Assembly 首次使用AVX 256位矢量会降低128位矢量和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指令) 基准程序代码: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
# 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代码。