c - 缺少掩码的 AVX-512 内在函数?

标签 c gcc intrinsics icc avx512

英特尔的内在指南 lists a number of intrinsics对于 AVX-512 K* 掩码说明,但似乎缺少一些:

  • KSHIFT{左/右}
  • 卡德
  • KTEST

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

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

    我是否没有查看上述指令的正确内在函数以及其他 __mmask* 类型变体,或者是否有另一种方法可以在不诉诸内联汇编的情况下实现相同的目标?

    最佳答案

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

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

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

    第一阶段:骑士登陆

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

    当英特尔设计这些第一组 AVX512 内在函数时,他们继续为几乎所有内容添加内在函数 - 包括掩码寄存器。这就是为什么确实存在的掩码内在函数只有 16 位的原因。而且它们只涵盖了 Knights Landing 中存在的说明。 (虽然我无法解释为什么 KSHIFT 不见了)

    在 Knights Landing 上,面具操作很快(2 个周期)。但是在屏蔽寄存器和通用寄存器之间移动数据真的很慢(5 个周期)。因此,掩码操作在何处进行很重要,并且让用户更精细地控制在掩码寄存器和 GPR 之间来回移动内容是有意义的。

    第二阶段:天湖紫菜

    Skylake Purley 扩展了 AVX512 以覆盖字节粒度 channel 。这将掩码寄存器的宽度增加到完整的 64 位。第二轮还增加了KADDKTEST这在骑士登陆中不存在。

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

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

    编译器/语法:

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

    性能不一致:

    Skylake Purley 上的位交叉掩码指令很慢。虽然所有按位指令都是单周期的,KADD , KSHIFT , KUNPACK等...都是4个周期。但是在掩模和 GPR 之间移动只有 2 个周期。

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

    通过让编译器做出这个决定,就意味着编译器需要有这样的逻辑。英特尔编译器当前会生成 kadd在某些(罕见)情况下和家人。但海湾合作委员会没有。在 GCC 上,除了最琐碎的掩码操作外,所有操作都将移至 GPR 并在那里完成。

    最后的想法:

    在 Skylake Purley 发布之前,我个人已经编写了很多 AVX512 代码,其中包括很多 AVX512 掩码代码。这些是用某些性能假设(单周期延迟)编写的,结果在 Skylake Purley 上是错误的。

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

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

    更新:

    不清楚何时发生这种情况,但最新版本的 Intel Intrinsics Guide 有一组新的掩码内在函数,其中包含涵盖所有指令和宽度的新命名约定。这些新的内在特性取代了旧的内在特性。

    所以这解决了整个问题。尽管编译器支持的范围仍然不确定。

    例子:

  • _kadd_mask64()
  • _kshiftri_mask32()
  • _cvtmask16_u32()取代 _mm512_mask2int()
  • 关于c - 缺少掩码的 AVX-512 内在函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45167997/

    相关文章:

    c - .pem openssl 中证书的十六进制详细信息

    c - 编译 CUDA 时出错

    c++ - 快速将 2 个 double 数组交织成具有 2 个 float 和 1 个 int(循环不变)成员的结构数组,并使用 SIMD double->float 转换?

    Arm Neon Intrinsics 与手工组装

    C++ 模板无法在 Linux GCC 上编译

    c++ - AVX 循环矢量化错误

    使用 swap context() 和自定义 yield 函数进行上下文切换

    c - 如何让用户以任意顺序输入命令行参数?

    c++ - 在 Linux 中以 C/C++ 编程方式更改文件的创建时间戳

    c - -D MACRO 和#define MACRO 的优先级