缺少面具的AVX-512内在函数?

英特尔的内在指南列出了 AVX-512 K *掩码指令的一些内在函数 ,但似乎有一些缺失:

  • KSHIFT {L / R}
  • KADD
  • KTEST

英特尔开发人员手册声称内在函数不是必需的,因为它们是由编译器自动生成的。 一个人怎么做呢? 如果这意味着__mmask *类型可以被视为常规整数,那么它会很有意义,但是像mask << 4这样的测试似乎会导致编译器将掩码移动到常规寄存器,移动它,然后再移回到面具。 这是使用Godbolt最新的GCC和ICC -O2 -mavx512bw

另外有趣的是,内在函数只处理__mmask16而不是其他类型。 我没有测试太多,但看起来ICC并不介意采用不正确的类型,但GCC似乎确实尝试确保掩码中只有16位,如果你使用内在函数。

我是不是在寻找上述指令的正确内在函数,以及其他__mmask *类型变体,还是有其他方法可以实现相同的东西而不需要求助于内联汇编?

英特尔的文档称,“没有必要,因为它们是由编译器自动生成的”实际上是正确的。 然而,它并不令人满意。

但要理解为什么它是这样的,你需要看看AVX512的历史。 虽然这些信息都不是官方信息,但根据证据强烈暗示。


掩码内在函数状态陷入困境的原因可能是因为AVX512在多个阶段“推出”而没有足够的前瞻性规划到下一阶段。

第1阶段:骑士降落

Knights Landing增加了512位寄存器,只有32位和64位数据粒度。 因此,掩码寄存器永远不需要宽于16位。

当英特尔设计这些第一套AVX512内在函数时,他们继续为几乎所有内容添加了内在函数 – 包括掩码寄存器。 这就是为什么存在的掩码内在函数只有16位。 它们仅涵盖Knights Landing中存在的指令。 (虽然我无法解释为什么缺少KSHIFT

在Knights Landing上,掩模操作很快(2个周期)。 但是,在掩码寄存器和通用寄存器之间移动数据非常慢(5个周期)。 因此,在完成掩码操作的地方很重要,并且让用户能够更精细地控制在掩码寄存器和GPR之间来回移动内容。

第二阶段: Skylake Purley

Skylake Purley将AVX512扩展到覆盖字节粒状通道。 这会将屏蔽寄存器的宽度增加到完整的64位。 第二轮还增加了骑士登陆中不存在的KADDKTEST

这些新的掩码指令(现有的KADDKTEST和64位扩展)是缺少其内在对应的指令。


虽然我们不确切地知道他们为什么会失踪,但有一些强有力的证据支持它:

编译/语法:

在Knights Landing上,相同的掩模内在函数用于8位和16位掩模。 没有办法区分它们。 通过将它们扩展到32位和64位,它使得混乱变得更糟。 换句话说,英特尔没有正确设计掩模内在函数。 他们决定完全放弃它们而不是修复它们。

表现不一致:

Skylake Purley的比特交叉掩码指令很慢。 虽然所有逐位指令都是单周期,但KADDKSHIFTKUNPACK等都是4个周期。 但是在掩模和GPR之间移动只有2个周期。

因此,将它们移动到GPR中以执行它们并将它们移回后通常会更快。 但程序员不太可能知道这一点。 因此,英特尔选择让编译器做出这个决定,而不是让用户完全控制掩码寄存器。

通过使编译器做出这个决定,这意味着编译器需要具有这样的逻辑。 英特尔编译器目前正在执行,因为它将在某些(罕见)情况下生成kadd和family。 但海湾合作委员会没有。 在GCC上,除了最琐碎的掩码操作之外的所有操作都将被移动到GPR并在那里完成。


最后的想法:

在Skylake Purley发布之前,我个人编写了很多AVX512代码,其中包含很多AVX512掩码。 这些都是根据Skylake Purley的某些性能假设(单周期延迟)编写的。

根据我自己在Skylake X上的测试,我的一些依赖于位交叉操作的掩码内部代码比编译器生成的版本更慢,后者将它们移动到GPR并返回。 当然,原因是KADDKSHIFT是4个周期而不是1个周期。

当然,如果英特尔确实提供内在函数来为我们提供我想要的控制权,我更愿意。 但如果你不知道自己在做什么,那么在这里(就性能而言)很容易出错。