Skylake是否需要vzeroupper来恢复Turbo时钟,以便在仅读取ZMM寄存器并写入k掩码的512位指令后恢复?

10

将ZMM寄存器写入可能会让Skylake-X(或类似的)CPU处于降低的最大睿频状态,可能会无限期地保持这种状态。 (SIMD instructions lowering CPU frequencyDynamically 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,xmm16vpcmpeqb与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 字节 进行电源门控。有一些用例,例如使用 kshiftkunpack 来处理掩码的 64 位块(用于加载/存储或传输到整数寄存器),即使您只使用 AVX512VL 的 YMM 或 XMM 寄存器将其生成或使用时也是如此。


PS:Xeon Phi 不受这些影响;它不会在运行其他代码时超频到重度


1
是的,仅仅像 zmm{k0} 或者 k1{k0} 这样使用它作为掩码是不可能的,因为在操作掩码字段中编码意味着没有掩码(相当于全部为 1)。但是作为比较-转换掩码或者 kshift 的目标,或者其他任何额外的掩码寄存器,你都可以使用它。vpcmpeqb k1{k0}, zmm0, zmm1 是无法编码的,但是 k0{k1} 可以。当然,纯粹的 k0 也可以。 - Peter Cordes
1
@BeeOnRope:啊,是的,那是错误的。它的编码是特殊的,而不是内容。就像[rbp]作为寻址模式是无法编码的(没有disp8),因为该编码实际上意味着[disp32][RIP+rel32] - Peter Cordes
1
@PeterCordes 我认为这与上下文切换标志无关。AVX512上下文切换标志ZMM_HI256_state用于zmm0-15,HI16_ZMM_state用于所有xmm/ymm/zmm16-31,以及Opmask_state用于k0-7(vzeroupper确实会清除ZMM_HI256_state,所以您对更快的上下文切换是正确的)。问题在于,由于HI16_ZMM_state甚至设置为xmm16,因此我无法想象它被劫持频率许可证。 - Noah
1
@PeterCordes 您可以轻松测试此功能,因为内核会跟踪上下文切换是否包含avx512寄存器,可在/proc/${pid}/arch_status中访问。对zmm0的验证写入(包括零惯用法xor)将设置ZMM_HI256_statevzeroupper将清除ZMM_HI256_state。任何写入(包括零习惯用法)到xmm/ymm/zmm16-31都将保持HI16_ZMM_state无限期设置。还有,从xmm/ymm/zmm16寄存器中读取(也称为vpxorq %zmm16,%zmm16,%zmm0)不会设置HI16_ZMM_state - Noah
1
@BeeOnRope 关于“保存和恢复寄存器的上下文切换是否有效地将状态翻转为清洁状态,因为也许压缩的xsave会检查所有的上限是否为零?”不是这样的。写入(任何类型的写入,甚至是对zmm0-15进行零异或操作)都会设置ZMM_HI256_state,并且zmm0-15将与下一个上下文切换一起存储。修改后的优化似乎明确地更新了状态组件。 - Noah
显示剩余25条评论
1个回答

6
不,如果您在使用一个zmm寄存器作为比较操作数之一时,将vpcmpeqb指令加载到掩码寄存器中不会触发缓慢模式,至少在SKX上是如此。这也适用于任何其他只读取512位关键寄存器(关键寄存器为zmm0 - zmm15)的指令(就我测试而言)。例如,vpxord zmm16,zmm0,zmm1也不会破坏上限,因为虽然它涉及关键寄存器zmm1zmm0,但它只从它们中读取,同时写入非关键寄存器zmm16

我在Xeon W-2104上使用avx-turbo进行测试,其名义速度为3.2 GHz,L1 turbo许可证(AVX2 turbo)为2.8 GHz,L2许可证(AVX-512 turbo)为2.4 GHz。我使用--dirty-upper选项在每次测试前弄脏上部寄存器,使用vpxord zmm15, zmm14, zmm15。这会导致任何使用任何SIMD寄存器的测试(包括标量SSE FP)以较慢的2.8 GHz速度运行,如这些结果所示(查看A/M-MHz列以获取CPU频率):

CPUID highest leaf  : [16h]
Running as root     : [YES]
MSR reads supported : [YES]
CPU pinning enabled : [YES]
CPU supports AVX2   : [YES]
CPU supports AVX-512: [YES]
cpuid = eax = 2, ebx = 266, ecx = 0, edx = 0
cpu: family = 6, model = 85, stepping = 4
tsc_freq = 3191.8 MHz (from calibration loop)
CPU brand string: Intel(R) Xeon(R) W-2104 CPU @ 3.20GHz
4 available CPUs: [0, 1, 2, 3]
4 physical cores: [0, 1, 2, 3]
Will test up to 1 CPUs
Cores | ID                  | Description                     | OVRLP1 | OVRLP2 | OVRLP3 | Mops | A/M-ratio | A/M-MHz | M/tsc-ratio
1     | pause_only          | pause instruction               |  1.000 |  1.000 | 1.000  | 2256 |      0.99 |    3173 | 1.00       
1     | ucomis_clean        | scalar ucomis (w/ vzeroupper)   |  1.000 |  1.000 | 1.000  |  790 |      1.00 |    3192 | 1.00       
1     | ucomis_dirty        | scalar ucomis (no vzeroupper)   |  1.000 |  1.000 | 1.000  |  466 |      0.88 |    2793 | 1.00       
1     | scalar_iadd         | Scalar integer adds             |  1.000 |  1.000 | 1.000  | 3192 |      0.99 |    3165 | 1.00       
1     | avx128_iadd         | 128-bit integer serial adds     |  1.000 |  1.000 | 1.000  | 2793 |      0.88 |    2793 | 1.00       
1     | avx256_iadd         | 256-bit integer serial adds     |  1.000 |  1.000 | 1.000  | 2793 |      0.87 |    2793 | 1.00       
1     | avx512_iadd         | 512-bit integer adds            |  1.000 |  1.000 | 1.000  | 2794 |      0.88 |    2793 | 1.00       
1     | avx128_iadd_t       | 128-bit integer parallel adds   |  1.000 |  1.000 | 1.000  | 8380 |      0.88 |    2793 | 1.00       
1     | avx256_iadd_t       | 256-bit integer parallel adds   |  1.000 |  1.000 | 1.000  | 8380 |      0.88 |    2793 | 1.00       
1     | avx128_mov_sparse   | 128-bit reg-reg mov             |  1.000 |  1.000 | 1.000  | 2793 |      0.88 |    2793 | 1.00       
1     | avx256_mov_sparse   | 256-bit reg-reg mov             |  1.000 |  1.000 | 1.000  | 2793 |      0.88 |    2793 | 1.00       
1     | avx512_mov_sparse   | 512-bit reg-reg mov             |  1.000 |  1.000 | 1.000  | 2794 |      0.87 |    2793 | 1.00       
1     | avx128_merge_sparse | 128-bit reg-reg merge mov       |  1.000 |  1.000 | 1.000  | 2793 |      0.88 |    2793 | 1.00       
1     | avx256_merge_sparse | 256-bit reg-reg merge mov       |  1.000 |  1.000 | 1.000  | 2793 |      0.88 |    2793 | 1.00       
1     | avx512_merge_sparse | 512-bit reg-reg merge mov       |  1.000 |  1.000 | 1.000  | 2794 |      0.88 |    2793 | 1.00       
1     | avx128_vshift       | 128-bit variable shift (vpsrld) |  1.000 |  1.000 | 1.000  | 2793 |      0.88 |    2793 | 1.00       
1     | avx256_vshift       | 256-bit variable shift (vpsrld) |  1.000 |  1.000 | 1.000  | 2793 |      0.88 |    2793 | 1.00       
1     | avx512_vshift       | 512-bit variable shift (vpsrld) |  1.000 |  1.000 | 1.000  | 2794 |      0.88 |    2793 | 1.00       
1     | avx128_vshift_t     | 128-bit variable shift (vpsrld) |  1.000 |  1.000 | 1.000  | 5587 |      0.88 |    2793 | 1.00       
1     | avx256_vshift_t     | 256-bit variable shift (vpsrld) |  1.000 |  1.000 | 1.000  | 5588 |      0.88 |    2793 | 1.00       
1     | avx512_vshift_t     | 512-bit variable shift (vpsrld) |  1.000 |  1.000 | 1.000  | 2794 |      0.88 |    2793 | 1.00       
1     | avx128_imul         | 128-bit integer muls            |  1.000 |  1.000 | 1.000  |  559 |      0.88 |    2793 | 1.00       
1     | avx256_imul         | 256-bit integer muls            |  1.000 |  1.000 | 1.000  |  559 |      0.88 |    2793 | 1.00       
1     | avx512_imul         | 512-bit integer muls            |  1.000 |  1.000 | 1.000  |  559 |      0.88 |    2793 | 1.00       
1     | avx128_fma_sparse   | 128-bit 64-bit sparse FMAs      |  1.000 |  1.000 | 1.000  | 2793 |      0.88 |    2793 | 1.00       
1     | avx256_fma_sparse   | 256-bit 64-bit sparse FMAs      |  1.000 |  1.000 | 1.000  | 2793 |      0.88 |    2793 | 1.00       
1     | avx512_fma_sparse   | 512-bit 64-bit sparse FMAs      |  1.000 |  1.000 | 1.000  | 2793 |      0.88 |    2793 | 1.00       
1     | avx128_fma          | 128-bit serial DP FMAs          |  1.000 |  1.000 | 1.000  |  698 |      0.88 |    2793 | 1.00       
1     | avx256_fma          | 256-bit serial DP FMAs          |  1.000 |  1.000 | 1.000  |  698 |      0.87 |    2793 | 1.00       
1     | avx512_fma          | 512-bit serial DP FMAs          |  1.000 |  1.000 | 1.000  |  698 |      0.88 |    2793 | 1.00       
1     | avx128_fma_t        | 128-bit parallel DP FMAs        |  1.000 |  1.000 | 1.000  | 4789 |      0.75 |    2394 | 1.00       
1     | avx256_fma_t        | 256-bit parallel DP FMAs        |  1.000 |  1.000 | 1.000  | 4790 |      0.75 |    2394 | 1.00       
1     | avx512_fma_t        | 512-bit parallel DP FMAs        |  1.000 |  1.000 | 1.000  | 2394 |      0.75 |    2394 | 1.00       
1     | avx512_vpermw       | 512-bit serial WORD permute     |  1.000 |  1.000 | 1.000  |  466 |      0.88 |    2793 | 1.00       
1     | avx512_vpermw_t     | 512-bit parallel WORD permute   |  1.000 |  1.000 | 1.000  | 1397 |      0.87 |    2793 | 1.00       
1     | avx512_vpermd       | 512-bit serial DWORD permute    |  1.000 |  1.000 | 1.000  |  931 |      0.87 |    2793 | 1.00       
1     | avx512_vpermd_t     | 512-bit parallel DWORD permute  |  1.000 |  1.000 | 1.000  | 2793 |      0.88 |    2793 | 1.00       

唯一以全速运行的测试是标量整数加法,它根本没有使用SSE/AVX寄存器,以及scalar ucomis (w/ vzeroupper),每个测试都有一个显式的vzeroupper,因此不会执行脏的上部分。
然后,我将脏指令更改为您感兴趣的vpcmpeqb k0, zmm0, [rsp]指令。新的结果:
Cores | ID                  | Description                     | OVRLP1 | OVRLP2 | OVRLP3 | Mops | A/M-ratio | A/M-MHz | M/tsc-ratio
1     | pause_only          | pause instruction               |  1.000 |  1.000 | 1.000  | 2256 |      1.00 |    3192 | 1.00       
1     | ucomis_clean        | scalar ucomis (w/ vzeroupper)   |  1.000 |  1.000 | 1.000  |  790 |      1.00 |    3192 | 1.00       
1     | ucomis_dirty        | scalar ucomis (no vzeroupper)   |  1.000 |  1.000 | 1.000  |  790 |      1.00 |    3192 | 1.00       
1     | scalar_iadd         | Scalar integer adds             |  1.000 |  1.000 | 1.000  | 3193 |      1.00 |    3192 | 1.00       
1     | avx128_iadd         | 128-bit integer serial adds     |  1.000 |  1.000 | 1.000  | 3193 |      1.00 |    3190 | 1.00       
1     | avx256_iadd         | 256-bit integer serial adds     |  1.000 |  1.000 | 1.000  | 3193 |      1.00 |    3192 | 1.00       
1     | avx512_iadd         | 512-bit integer adds            |  1.000 |  1.000 | 1.000  | 2794 |      0.88 |    2793 | 1.00       
1     | avx128_iadd_t       | 128-bit integer parallel adds   |  1.000 |  1.000 | 1.000  | 9575 |      1.00 |    3192 | 1.00       
1     | avx256_iadd_t       | 256-bit integer parallel adds   |  1.000 |  1.000 | 1.000  | 9577 |      1.00 |    3192 | 1.00       
1     | avx128_mov_sparse   | 128-bit reg-reg mov             |  1.000 |  1.000 | 1.000  | 3193 |      1.00 |    3192 | 1.00       
1     | avx256_mov_sparse   | 256-bit reg-reg mov             |  1.000 |  1.000 | 1.000  | 3193 |      1.00 |    3192 | 1.00       
1     | avx512_mov_sparse   | 512-bit reg-reg mov             |  1.000 |  1.000 | 1.000  | 2793 |      0.88 |    2793 | 1.00       
1     | avx128_merge_sparse | 128-bit reg-reg merge mov       |  1.000 |  1.000 | 1.000  | 3193 |      1.00 |    3192 | 1.00       
1     | avx256_merge_sparse | 256-bit reg-reg merge mov       |  1.000 |  1.000 | 1.000  | 3193 |      1.00 |    3192 | 1.00       
1     | avx512_merge_sparse | 512-bit reg-reg merge mov       |  1.000 |  1.000 | 1.000  | 2793 |      0.88 |    2793 | 1.00       
1     | avx128_vshift       | 128-bit variable shift (vpsrld) |  1.000 |  1.000 | 1.000  | 3193 |      1.00 |    3192 | 1.00       
1     | avx256_vshift       | 256-bit variable shift (vpsrld) |  1.000 |  1.000 | 1.000  | 3193 |      1.00 |    3192 | 1.00       
1     | avx512_vshift       | 512-bit variable shift (vpsrld) |  1.000 |  1.000 | 1.000  | 2794 |      0.88 |    2793 | 1.00       
1     | avx128_vshift_t     | 128-bit variable shift (vpsrld) |  1.000 |  1.000 | 1.000  | 6386 |      1.00 |    3192 | 1.00       
1     | avx256_vshift_t     | 256-bit variable shift (vpsrld) |  1.000 |  1.000 | 1.000  | 6386 |      1.00 |    3192 | 1.00       
1     | avx512_vshift_t     | 512-bit variable shift (vpsrld) |  1.000 |  1.000 | 1.000  | 2794 |      0.88 |    2793 | 1.00       
1     | avx128_imul         | 128-bit integer muls            |  1.000 |  1.000 | 1.000  |  638 |      1.00 |    3192 | 1.00       
1     | avx256_imul         | 256-bit integer muls            |  1.000 |  1.000 | 1.000  |  639 |      1.00 |    3192 | 1.00       
1     | avx512_imul         | 512-bit integer muls            |  1.000 |  1.000 | 1.000  |  559 |      0.88 |    2793 | 1.00       
1     | avx128_fma_sparse   | 128-bit 64-bit sparse FMAs      |  1.000 |  1.000 | 1.000  | 3193 |      1.00 |    3192 | 1.00       
1     | avx256_fma_sparse   | 256-bit 64-bit sparse FMAs      |  1.000 |  1.000 | 1.000  | 3193 |      1.00 |    3192 | 1.00       
1     | avx512_fma_sparse   | 512-bit 64-bit sparse FMAs      |  1.000 |  1.000 | 1.000  | 2793 |      0.87 |    2793 | 1.00       
1     | avx128_fma          | 128-bit serial DP FMAs          |  1.000 |  1.000 | 1.000  |  798 |      1.00 |    3192 | 1.00       
1     | avx256_fma          | 256-bit serial DP FMAs          |  1.000 |  1.000 | 1.000  |  798 |      1.00 |    3192 | 1.00       
1     | avx512_fma          | 512-bit serial DP FMAs          |  1.000 |  1.000 | 1.000  |  698 |      0.88 |    2793 | 1.00       
1     | avx128_fma_t        | 128-bit parallel DP FMAs        |  1.000 |  1.000 | 1.000  | 6384 |      1.00 |    3192 | 1.00       
1     | avx256_fma_t        | 256-bit parallel DP FMAs        |  1.000 |  1.000 | 1.000  | 5587 |      0.87 |    2793 | 1.00       
1     | avx512_fma_t        | 512-bit parallel DP FMAs        |  1.000 |  1.000 | 1.000  | 2394 |      0.75 |    2394 | 1.00       
1     | avx512_vpermw       | 512-bit serial WORD permute     |  1.000 |  1.000 | 1.000  |  466 |      0.87 |    2793 | 1.00       
1     | avx512_vpermw_t     | 512-bit parallel WORD permute   |  1.000 |  1.000 | 1.000  | 1397 |      0.88 |    2793 | 1.00       
1     | avx512_vpermd       | 512-bit serial DWORD permute    |  1.000 |  1.000 | 1.000  |  931 |      0.88 |    2793 | 1.00       
1     | avx512_vpermd_t     | 512-bit parallel DWORD permute  |  1.000 |  1.000 | 1.000  | 2794 |      0.88 |    2793 | 1.00     

大多数测试现在以全速运行。仍然以2.8 GHz(或在某些情况下以2.4 GHz进行并行512位FMAs)运行的测试是实际使用512位向量或使用256位向量和重型FP指令(如FMA)的测试,这是预期的。


注释不是用于扩展讨论的;此对话已移至聊天室 - Samuel Liew

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接