英特尔的文档说,“没有必要,因为它们是由编译器自动生成的”实际上是正确的。然而,它并不令人满意。
但要了解为什么会这样,您需要查看 AVX512 的历史。虽然这些信息都不是官方的,但它是根据证据强烈暗示的。
掩码内部函数的状态陷入现在这样一团糟的原因可能是因为 AVX512 在多个阶段“推出”而没有对下一阶段进行足够的前瞻性计划。
第一阶段:骑士登陆
Knights Landing 添加了 512 位寄存器,只有 32 位和 64 位数据粒度。因此,掩码寄存器永远不需要超过 16 位。
当英特尔设计这些第一组 AVX512 内部函数时,他们继续为几乎所有内容添加内部函数 - 包括掩码寄存器。这就是为什么确实存在的掩码内在函数只有 16 位。而且它们仅涵盖 Knights Landing 中存在的说明。(虽然我无法解释为什么KSHIFT
失踪)
在 Knights Landing 上,掩码操作很快(2 个周期)。但是在掩码寄存器和通用寄存器之间移动数据非常慢(5 个周期)。因此,在哪里完成掩码操作很重要,让用户更细粒度地控制掩码寄存器和 GPR 之间来回移动内容是有意义的。
第 2 阶段: Skylake Purley
Skylake Purley 扩展了 AVX512 以覆盖字节粒度通道。这将掩码寄存器的宽度增加到完整的 64 位。这第二轮还添加了骑士登陆KADD
中KTEST
不存在的。
这些新的掩码指令(现有指令的 、 和 64 位扩展)是缺少其内在对应物的KADD
指令。KTEST
虽然我们不知道他们失踪的确切原因,但有一些强有力的证据支持它:
编译器/语法:
在 Knights Landing 中,相同的掩码内在函数用于 8 位和 16 位掩码。没有办法区分它们。通过将它们扩展到 32 位和 64 位,情况变得更糟。换句话说,英特尔一开始就没有正确设计掩码内部函数。他们决定完全放弃它们而不是修复它们。
性能不一致:
Skylake Purley 上的位交叉掩码指令很慢。虽然所有按位指令都是单周期的,KADD
但 , KSHIFT
, KUNPACK
, 等......都是 4 个周期。但是在面罩和 GPR 之间移动只有 2 个周期。
正因为如此,将它们移入 GPR 执行它们并将它们移回通常更快。但是程序员不太可能知道这一点。因此,英特尔没有让用户完全控制屏蔽寄存器,而是选择让编译器做出这个决定。
让编译器做出这个决定,就意味着编译器需要有这样的逻辑。英特尔编译器当前会kadd
在某些(罕见)情况下生成和族。但 GCC 没有。在 GCC 上,除了最琐碎的掩码操作之外,所有操作都将移至 GPR 并在那里完成。
最后的想法:
在 Skylake Purley 发布之前,我个人编写了很多 AVX512 代码,其中包括很多 AVX512 掩码代码。这些是在某些性能假设(单周期延迟)下编写的,这些假设在 Skylake Purley 上被证明是错误的。
根据我自己在 Skylake X 上的测试,我的一些依赖于位交叉操作的掩码固有代码比编译器生成的将它们移动到 GPR 并返回的版本要慢。原因当然是4 个周期而不是 1 个KADD
。KSHIFT
当然,我更喜欢英特尔确实提供了内在函数来给我们想要的控制。但是如果你不知道自己在做什么,这里很容易出错(就性能而言)。
更新:
目前还不清楚这是什么时候发生的,但最新版本的英特尔内部函数指南有一组新的掩码内部函数,其新的命名约定涵盖了所有指令和宽度。这些新的内在函数取代了旧的内在函数。
所以这解决了整个问题。尽管编译器支持的程度仍然不确定。
例子:
_kadd_mask64()
_kshiftri_mask32()
_cvtmask16_u32()
取代_mm512_mask2int()