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

zin*_*nga 6 c gcc icc intrinsics avx512

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

  • KSHIFT {L/R}
  • KADD
  • KTEST

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

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

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

Mys*_*ial 8

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

但要理解为什么它是这样的,你需要看看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位.第二轮也增加了KADD,KTEST并且在骑士登陆中不存在.

这些新的面具指令(KADD,KTEST,和现有的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.

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

  • 对于没有生成最佳序列的编译器,AVX512仍然是新的,并且优化器在它们方面仍然不成熟.所以最后,如果你想要完全控制,你需要内联汇编.即使在那时,ICC中也存在一些缺陷,使其不那么有用. (2认同)
  • @Mysticial:如果有机会,您可以通过检查与在已知端口上运行的其他指令是否存在资源冲突来检查端口,而无需性能计数器。例如,使用 shuffle + kshift 吞吐量检查 p5。p1 具有 `imul` + kshift 吞吐量。p0 与 `movd eax, xmm0` 或 `pmovmskb` + kshift 吞吐量。(或者我猜想,使用 512b 指令关闭 p1 进行向量操作,很多东西只在 p0 上运行,比如 `pmullw`。) (2认同)
  • @PeterCordes看起来有人击败了Agner:https://github.com/InstLatx64/InstLatx64/blob/master/AVX512_SKX_PortAssign_v10_PUB.ods (2认同)