Skylake 是否需要 vzeroupper 才能在仅读取 ZMM 寄存器并写入 k 掩码的 512 位指令后恢复涡轮时钟?

Does Skylake need vzeroupper for turbo clocks to recover after a 512-bit instruction that only reads a ZMM register, writing a k mask?

写入 ZMM 寄存器会使 Skylake-X(或类似设备)CPU 无限期地处于降低最大涡轮增压的状态。 (SIMD instructions lowering CPU frequency and Dynamically determining where a rogue AVX-512 instruction is executing)想必Ice Lake也差不多

(解决方法:根据我在 中引用的@BeeOnRope 的评论,zmm16..31 不是问题 所以这个 strlen 可以只使用 vpxord xmm16,xmm16,xmm16vpcmpeqb 与 zmm16。)

如果您有硬件,如何进行测试:

@BeeOnRope 发布 test code in an RWT thread:将 vbroadcastsd zmm15, [zero_dp] 替换为 vpcmpeqb k0, zmm0, [rdi] 作为 "dirtying" 指令,然后查看 运行 之后的循环是慢还是快.


我假设执行任何 512 位 uop 将暂时触发减少的 turbo(同时关闭矢量 ALU uops 的端口 1,而 512 位 uop 实际上在后端),但问题是:Will如果在 读取 ZMM 寄存器后从未使用 vzeroupper,CPU 会自行恢复?

(And/or 以后的 SSE 或 AVX 指令会有转换惩罚或错误依赖吗?)

具体来说,像这样使用 insns 的 strlen 在返回之前需要 vzeroupper 吗?(实际上在任何真实的 CPU 上, and/or 如英特尔记录的面向未来的最佳实践。)假设以后的指令可能包括非 VEX SSE and/or 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?

(受 中的 strlen 的启发,如果对其向量 reg 归零进行了适当优化以使用更短的 VEX 而不是 EVEX 指令,它会看起来像这样。)


关键指令是 vpcmpeqb k0, zmm0, [rdi],它在 SKX 或 CNL 上解码为 2 个独立的微指令 (not micro-fused: retire-slots = 2.0):一个 512 位加载(到一个 512 位物理寄存器?)和一个ALU 比较成屏蔽寄存器。

但是没有架构 ZMM 寄存器是明确写入的,只能读取。所以大概至少 xsave/xrstor 会清除任何 "dirty upper" 条件,如果在此之后存在的话。 (这不会在 Linux 上发生,除非在该内核上有一个实际的上下文切换到不同的用户-space 进程,或者线程迁移;仅仅进入内核中断不会导致它。所以这在主流 OS 下实际上仍然可以测试,如果你有硬件;我没有。)

我可以想象 SKX/CNL、and/or 冰湖的可能性:


我假设 kmovq 不会降低最大涡轮增压或触发任何其他 512 位 uop 效果。屏蔽寄存器的高 32 位通常只与 AVX512BW 一起用于 64 字节向量,但大概它们不会单独对屏蔽寄存器的前 32 位进行电源门控,只有前 32 字节 的矢量 regs。有一些用例,例如使用 kshiftkunpack 来处理 64 位掩码块(对于 load/store 或转移到整数 regs),即使您只生成或使用它们 32使用带有 YMM 或 XMM 寄存器的 AVX512VL 一次位。


PS:Xeon Phi 不受这些影响;当 运行 其他代码时,它不是为了超越重型 AVX512 而构建的,因为它是为 运行 AVX512 制作的。事实上 vzeroupper 非常慢,不推荐在 KNL / KNM 上使用。

我的示例使用 AVX512BW 的事实与问题无关,但所有主流(非 Xeon Phi)CPU 具有 AVX512 的都具有 AVX512BW。它只是一个很好的真实用例,使用 AVX512BW 排除 KNL 的事实是无关紧要的。

No,如果使用 zmm 寄存器作为 compa[=48] 之一,将 vpcmpeqb 写入掩码寄存器不会触发慢速模式=]ds,至少在 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。在使用 vpxord zmm15, zmm14, zmm15 进行每次测试之前,我使用 --dirty-upper 选项弄脏鞋面。这会导致任何使用任何 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       

全速 运行 的唯一测试是 Scalar integer adds,它根本没有 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(或者在一种情况下并行 512 位 FMA 为 2.4 GHz)的那些实际上使用 512 位向量,或者使用 256 位向量和像 FMA 这样的重 FP 指令,因为预期。