将ZMM寄存器写入可能会让Skylake-X(或类似的)CPU处于降低的最大睿频状态,可能会无限期地保持这种状态。 (SIMD instructions lowering CPU frequency 和 Dynamically determining where a rogue AVX-512 instruction is executing)。据推测,Ice Lake也是类似情况。
(解决方法:zmm16..31不受影响,根据@BeeOnRope在Is it useful to use VZEROUPPER if your program+libraries contain no SSE instructions?中引用的评论。
因此该strlen可以只使用vpxord xmm16,xmm16,xmm16
和vpcmpeqb
与zmm16.)
如何在具有硬件的情况下测试:
@BeeOnRope发布了在RWT线程中的测试代码:将vbroadcastsd zmm15, [zero_dp]
替换为vpcmpeqb k0, zmm0, [rdi]
作为“脏”指令,看看之后的循环是运行缓慢还是快速。
我假设执行任何512位uop都会暂时触发降低的睿频(同时在512位uop实际在后端时关闭端口1以进行向量ALU uop),但问题是:如果您在只读取 ZMM寄存器后从未使用vzeroupper
,CPU是否会自行恢复?
(和/或后续的SSE或AVX指令是否有转换惩罚或虚假依赖关系?)
具体而言,使用这种指令的strlen
在返回之前是否需要vzeroupper
?(在任何真实的CPU上实践,并且/或者根据英特尔文档记录的最佳实践来看。)假设后续指令可能包括非VEX SSE和/或VEX编码AVX1/2,而不仅限于GP整数,以防止出现脏-上限256的情况导致睿频降低。
; check 64 bytes for zero, strlen building block.
vpxor xmm0,xmm0,xmm0 ; zmm0 = 0 using AVX1 implicit zero-extension
vpcmpeqb k0, zmm0, [rdi] ; 512-bit load + ALU, not micro-fused
;kortestq k0,k0 / jnz or whatever
kmovq rax, k0
tzcnt rax, rax
;vzeroupper before lots of code that goes a long time before another 512-bit uop?
受启发于 AVX512BW: handle 64-bit mask in 32-bit code with bsf / tzcnt? 中的 strlen 函数,如果对其向量寄存器进行清零操作时能够适当地优化为使用更短的 VEX 指令而非 EVEX 指令,则将如此编码。
关键指令是 vpcmpeqb k0, zmm0, [rdi]
,它在 SKX 或 CNL 上解码为 2 个单独的微操作 (未微合并: retire-slots = 2.0):一个 512 位负载 (进入一个 512 位物理寄存器?) 和一个用于比较 ALU 的掩码寄存器。
但没有任何体系结构级别的 ZMM 寄存器会被显式地写入,只有读取。因此,至少需要一个 xsave
/xrstor
才能清除任何“脏上”的状态(如果在此之后存在这种情况)。(在 Linux 上不会发生这种情况,除非在该核心上实际上下文切换到不同的用户空间进程,或线程迁移;仅进入内核以进行中断不会导致它。因此,如果您有硬件,则实际上仍然可以在主流操作系统下进行测试;而我没有。)
我能想象的 SKX/CNL 和/或 Ice Lake 的可能性:
- 没有长期影响:最大睿频速度的恢复与
vzeroupper
一样快 - 最大睿频受到限制,直到发生上下文切换。(
xrstor
或等效项清除了任何“脏上”状态标志,因为体系结构寄存器已清除)。 - 最大睿频受到限制,即使跨上下文切换时也是如此,就像运行
vaddps zmm0,zmm0,zmm0
一样。(保存的状态中设置了脏的上半部分标志,并随体系结构状态一起恢复。) 可能是因为xsaveopt
如果知道它们干净,就会跳过保存向量寄存器的上128或256个字节。
我认为 kmovq
不会降低最大睿频或触发任何其他 512 位微操作效应。掩码寄存器的上 32 位通常仅在 AVX512BW 中用于 64 字节向量,但假定它们不会单独对掩码寄存器的前 32 位进行电源门控,而是只对矢量寄存器的前 32 字节 进行电源门控。有一些用例,例如使用 kshift
或 kunpack
来处理掩码的 64 位块(用于加载/存储或传输到整数寄存器),即使您只使用 AVX512VL 的 YMM 或 XMM 寄存器将其生成或使用时也是如此。
PS:Xeon Phi 不受这些影响;它不会在运行其他代码时超频到重度
zmm{k0}
或者k1{k0}
这样使用它作为掩码是不可能的,因为在操作掩码字段中编码意味着没有掩码(相当于全部为 1)。但是作为比较-转换掩码或者kshift
的目标,或者其他任何额外的掩码寄存器,你都可以使用它。vpcmpeqb k1{k0}, zmm0, zmm1
是无法编码的,但是k0{k1}
可以。当然,纯粹的k0
也可以。 - Peter Cordes[rbp]
作为寻址模式是无法编码的(没有disp8
),因为该编码实际上意味着[disp32]
或[RIP+rel32]
。 - Peter CordesZMM_HI256_state
用于zmm0-15,HI16_ZMM_state
用于所有xmm/ymm/zmm16-31,以及Opmask_state
用于k0-7(vzeroupper
确实会清除ZMM_HI256_state
,所以您对更快的上下文切换是正确的)。问题在于,由于HI16_ZMM_state
甚至设置为xmm16
,因此我无法想象它被劫持频率许可证。 - Noah/proc/${pid}/arch_status
中访问。对zmm0
的验证写入(包括零惯用法xor)将设置ZMM_HI256_state
。vzeroupper
将清除ZMM_HI256_state
。任何写入(包括零习惯用法)到xmm/ymm/zmm16-31都将保持HI16_ZMM_state
无限期设置。还有,从xmm/ymm/zmm16寄存器中读取(也称为vpxorq %zmm16,%zmm16,%zmm0
)不会设置HI16_ZMM_state
。 - Noahzmm0-15
进行零异或操作)都会设置ZMM_HI256_state
,并且zmm0-15
将与下一个上下文切换一起存储。修改后的优化似乎明确地更新了状态组件。 - Noah