AVX512BW vpcmpgtb 对其 K 结果执行指令

AVX512BW vpcmpgtb perform instruction on its K result

我想比较一个 ZMM 向量并使用其结果并执行 vpandn
AVX2,我这样做:

vpcmpgtb ymm0, ymm0, ymm1
vpandn  ymm0, ymm0, ymm2
vpxor   ymm0, ymm0, ymm3

但在 AVX512BWvpcmpgtb returns 中结果是 K
我应该如何执行 vpandn 然后 vpxorAVX512BW?

中的结果
vpcmpgtb k0, zmm0, zmm1
vpandn ??
vpxor ??

k个寄存器有单独的说明;他们的助记符都是以k开头的,所以很容易在the table of instructions, like kandnq k0, k0, k1.

中找到

以及kunpck...(连接,不是交织),kadd/kshiftkor/kand/knot/ kxor,甚至 kxnor(为 gather/scatter 生成 all-ones 的简便方法)。当然还有 kmov(包括 to/from 内存或 GP-integer),以及 kortestktest 用于分支。

它们的大小都是 byte/word/dword/qword 受影响的掩码位数,zero-extending 结果。 (在 Xeon Phi 上没有 AVX-512BW,只有字节和字的大小,因为 16 位覆盖了元素小到 dword 的 ZMM。但是所有带有 AVX-512 的主流 CPUs 都有 AVX-512BW,因此 64-位掩码寄存器。)


有时您可以将其折叠到另一个操作中以避免需要单独的指令来组合掩码。反转比较以便您可以直接使用 ktest 进行分支,或者如果您想要屏蔽,请使用 zero-masked compare-into-mask。 (不支持 Merge-masked compare/test 进入第三个现有掩码。)

AVX-512 整数比较将谓词作为立即数,而不是仅作为 eqgt 存在,因此您可以反转条件并使用 and 而不是需要andn。 (有符号与 unsigned vpcmpub 不同,也不同于任何以前的 x86 SIMD 扩展。因此,如果您之前添加 128 来翻转 pcmpgtb 的高位,则不需要不再那样并且可以做到 vpcmpub.)

vpcmpngtb   k1,    zmm3, zmm1     ; k0 can't be used for masking, only with k instructions
vpcmpeqb   k2{k1}, zmm4, zmm1     ; This is zero-masking even without {z}, because merge masking isn't supported for this

等同于(性能除外):

vpcmpngtb  k1,    zmm3, zmm1
vpcmpeqb   k2,    zmm4, zmm1
kand       k2,    k2, k1

也相当于 kandngt 比较作为 NOTed(第一个)操作数,就像你的问题一样。

k...掩码指令通常只能在端口0上运行,性能不佳。 https://uops.info/.

掩码比较(或其他指令)必须等待掩码寄存器输入准备就绪,然后才能开始处理其他操作数。您可能希望它支持延迟转发掩码,因为只在 write-back 处使用它们,但 IIRC 不支持。尽管如此,只有 1 条指令而不是 2 条指令仍然更好。两条指令中的第一条能够并行执行并不是更好,除非它是高延迟并且掩码操作是低延迟,并且您受到延迟限制。但通常 execution-unit 吞吐量在使用 512 位寄存器时更成为瓶颈。 (因为端口 1 上的向量 ALU 已关闭。)

一些 k 指令在当前 CPUs 上只有 1 个周期的延迟,而其他的是 4 个周期的延迟。 (例如 kshiftkunpck,以及 kadd。)


The intrinsics 对于这些掩码 compare-into-mask 指令是 _mm256_mask_cmp_ep[iu]_mask,带有一个 __mmask8/16/32/64 输入操作数(以及两个向量和一个直接谓词)和一个掩码 return值。像 asm 一样,他们使用 ..._mask_... 而不是 ..._maskz_... 尽管这是 zero-masking 而不是 merge-masking.


将蒙版应用于矢量

显然这个问题想要将掩码与另一个向量一起使用,而不仅仅是为 vpmovmskb 或其他东西获取掩码。 AVX-512 在写入矢量目标时具有 merge-masking 和 zmm0{k1} 以及 zero-masking 和 zmm0{k1}{z}。如果您了解 AVX2 asm 但还不了解 AVX-512 新内容的基础知识,请参阅 slides from Kirill Yukhin 介绍一系列 AVX-512 功能和它们的 asm 语法。

;; original code
  vpaddb       ymm0, ymm1, ymm2
  vpcmpgtb     ymm0, ymm0, ymm3    ; sum > y3 (signed compare)
  vpandn       ymm0, ymm0, ymm4    ; masked y4
  vpxor        ymm0, ymm1, ymm0    ; y0 = y1^y4 in bytes where compare was false
                                   ; y0 = y1  where it was true

在 AVX-512 CPU 上使用 256 位向量,可以使用 vpternlogd 替换最后 2 条指令(仍然使用 AVX2 compare-into-vector 只要避免 ymm16 ..31).不幸的是,AVX-512 根本没有 compare-into-vector,只有掩码。如果您的程序不会在 SIMD 循环中花费大量时间,256 位向量可能是一个不错的选择,especially on CPUs where the max-turbo penalty is higher for 512-bit vectors。 (对于整数向量来说没什么大不了的,乘法以外的 SIMD 整数是“轻”的,而不是“重”的)

对于 512 位向量,我们必须使用掩码。完全天真的 drop-in 方法是将掩码扩展回带有 vpmovm2b zmm0, k1 的向量,然后 vpandnq/vpxorq 不带掩码。或者 vpternlogd 在这种情况下,没有屏蔽仍然可以将总数减少到 4 条指令,结合 andn/xor.

A zero-masking vmovdqu8 zmm0{k1}{z}, zmm4 是替换 vpandn 的更好方法。或者在 xor 之后混合,使用掩码作为控制操作数。那仍然是 4 条指令,都需要一个执行单元。

如果可能的话,例如在 32 位元素的另一个问题中1,merge-masked XOR 会很好(在复制寄存器不变后,mov-elimination 可以工作2 如果你不能摧毁 zmm1)。

但是 AVX-512 没有 byte-masking for bitwise-booleans;只有 vpxord and vpxorq 允许在 32 位或 64 位元素中进行屏蔽。 AVX-512BW 只为 vmovdqu 添加了 byte/word-element 大小指令,以及即使没有掩码也关心边界的指令,如 vpaddbvpshufb.

我们 instruction-level 并行性的最佳选择是与比较并行异或,然后在比较掩码结果准备就绪后修复该结果。

  vpaddb     zmm0,    zmm1, zmm2
  vpcmpgtb   k1,      zmm0, zmm3   ; (sum > z3) signed compare, same as yours
  vpxord     zmm0,    zmm1, zmm4
  vmovdqu8   zmm0{k1}, zmm1        ; replace with z1 in bytes where (z1+z2 > z3)
        ; z0 = z1^z4 in bytes where compare was false
        ; z0 = z1 where it was true.

最终指令同样可以是 vpblendmb zmm0{k1}, zmm0, zmm1 (manual),这与 merge-masking 不同vmovdqu8 只能将混合结果写入第三个寄存器。

根据您要对 vpxord 结果执行的操作,您可以进一步优化周围的代码,如果它是更多按位布尔值,也许可以使用 vpternlogd。或者可能通过 merge-masking 或 zero-masking 变成其他东西。例如也许复制 zmm1 并在其中执行 merge-masked vpaddb,而不是进行混合。


另一种更糟糕的方法,instruction-level 并行度较低,是使用与 AVX2 代码相同的顺序(其中 more-ILP 方法需要更昂贵的 vpblendvb .)

; Worse ILP version, direct port of your AVX2 logic
  vpaddb     zmm0,    zmm1, zmm2
  vpcmpngtb  k1,      zmm0, zmm3   ; !(sum > z3) signed compare
  vmovdqu8   zmm0{k1}{z}, zmm4     ; zmm4 or 0, like your vpandn result
  vpxord     zmm0, zmm0, zmm1      ; z0 = z1^z4 in bytes where compare was true
                       ; leaving z0=z1 bytes where the mask was zero  (k1[i]==0)
         ; this is for the inverted compare, ngt = le condition

其中,每条指令都依赖于前一条指令的结果,所以从k1准备好到最后的zmm0准备好的总延迟是3个周期而不是4个。(较早的版本可以 运行 vpxordvpcmpb 并行,假设 ZMM4 准备得足够早。)

Zero-masking(和 merge-masking)vmovdqu8 在 Skylake-X 和 Alder Lake (https://uops.info/) 上有 3 个周期的延迟。与 vpblendmb 相同,但 vmovdqu32 和 64 具有 1 个周期的延迟。

vpxord 即使有屏蔽也有 1 个周期的延迟,但是 vpaddb 有 3 个周期的延迟有屏蔽 vs. 1 没有。因此,似乎 byte-masking 始终是 3 周期延迟 ,而 dword/qword 屏蔽与未屏蔽指令保持相同的延迟。但是吞吐量不受影响,所以只要你有足够的 instruction-level 并行度,out-of-order exec 可以隐藏延迟,如果它不是很长的 loop-carried dep 链。


脚注 1:更宽的元素允许屏蔽布尔值

这是为了将来使用不同元素大小的读者的利益。如果不需要,您绝对不想将字节元素扩展为双字,这样每个向量完成的工作将减少 1/4,只是为了通过 mov-elimination 节省 1 back-end uop:

; 32-bit elements would allow masked xor
; but there is no vpxorb

vpaddd     zmm0,    zmm1, zmm2
vpcmpngtd  k1,      zmm0, zmm3   ; !(sum > z3) signed compare
 ;vpxord    zmm1{k1}, zmm1, zmm4   ; if destroying ZMM1 is ok

vmovdqa64  zmm0,    zmm1         ; if not, copy first
vpxord     zmm0{k1}, zmm1, zmm4  ; z0 = z1^z4 in dwords where compare was true
                    ; leaving z0=z1 dwords where the mask was zero  (k1[i]==0)

脚注 2:

vmovdqu8 zmm0, zmm1 不需要执行单元。但是 vmovdqu8 zmm0{k1}{z}, zmm1 和其他 512 位 uops 一样,只能 运行 在当前 Intel CPUs 的端口 0 或 5 上,包括 Ice Lake 和 Alder Lake-P(在未禁用其 AVX-512 支持的系统)。

Ice Lake broke mov-elimination only for GP-integer, not vectors,因此寄存器的精确副本仍然比做任何掩蔽或其他工作便宜。只有两个 SIMD 执行端口使得 back-end 成为比使用 256 位向量的代码更常见的瓶颈,尤其是在 Ice Lake 上以及后来的 Ice Lake 5-wide front-end,6-wide in桤木湖/蓝宝石急流。

尽管如此,大多数代码都有重要的 load/store 和整数工作。