c - 缺少 mask 的AVX-512内部要素吗?

标签 c gcc intrinsics icc avx512

英特尔的内在函数指南lists a number of intrinsics适用于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添加了仅具有32位和64位数据粒度的512位寄存器。因此,掩码寄存器永远不需要比16位宽。

    英特尔设计第一批AVX512内部函数时,他们继续前进,并为几乎所有内容(包括掩码寄存器)添加了内部函数。这就是为什么存在的掩码内在函数只有16位的原因。并且它们仅涵盖Knights Landing中存在的说明。 (尽管我无法解释为什么缺少KSHIFT)

    在Knights Landing上,面罩操作非常快(2个周期)。但是在屏蔽寄存器和通用寄存器之间移动数据确实很慢(5个周期)。因此,在什么地方执行掩码操作很重要,因此有必要为用户提供更精细的控制,以便在掩码寄存器和GPR之间来回移动内容。

    第2阶段: Skylake Purley

    Skylake Purley扩展了AVX512的功能,以覆盖字节粒度的通道。这将掩码寄存器的宽度增加到全64位。第二轮还添加了Knights Landing中不存在的KADDKTEST

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

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

    编译器/语法:

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

    性能不一致:

    Skylake Purley上的位交叉掩码指令很慢。虽然所有按位指令都是单周期的,但是KADDKSHIFTKUNPACK等都是四个周期。但是在面罩和GPR之间移动只有2个周期。

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

    通过使编译器做出此决定,这意味着编译器需要具有这种逻辑。英特尔编译器当前正在执行此操作,因为它将在某些(罕见)情况下生成kadd和家族。但是海湾合作委员会却没有。在GCC上,除了最简单的蒙版操作外,所有其他操作都将移至GPR并在那里进行。

    最终想法:

    在Skylake Purley发行之前,我个人编写了很多AVX512代码,其中包括很多AVX512掩码代码。这些都是在某些性能假设(单周期延迟)下编写的,这些假设在Skylake Purley上被证明是错误的。

    通过我在Skylake X上的测试,发现一些依赖位交叉操作的掩码固有代码比编译器生成的将它们移至GPR并返回的版本要慢。当然,原因是KADDKSHIFT是4个周期而不是1个周期。

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

    更新:

    目前尚不清楚何时发生,但是最新版本的《英特尔内在指南》具有一组新的掩码内在函数,并具有覆盖所有指令和宽度的新命名约定。这些新的内在要素取代了旧的内在要素。

    因此,这解决了整个问题。尽管编译器支持的程度仍然不确定。

    例子:

  • _kadd_mask64()
  • _kshiftri_mask32()
  • _cvtmask16_u32()取代_mm512_mask2int()
  • 关于c - 缺少 mask 的AVX-512内部要素吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47348087/

    相关文章:

    c - 2 个 AVX-512 vector 元素的交错合并 - C 内在函数

    c - 删除堆栈给定索引处的元素,该索引是堆栈链表的一部分

    使用 open() 创建文件但无权在 C 中修改它

    c++ - Windows编程中的不同字符类型

    c - 'wrapping' 乘法溢出的结果是什么?

    c++ - 为什么 GCC 将对全局实例的构造函数的调用放入不同的部分(取决于目标)?

    gcc - 确定 Ravenscar 程序中堆栈使用情况的最佳实践

    c++ - C++ 中的 long long int 与 long int 与 int64_t

    c++ - 如何优化一个周期?

    llvm - 使用 LLVM C API 生成对内部函数的调用