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

Posted

技术标签:

【中文标题】缺少掩码的 AVX-512 内在函数?【英文标题】:Missing AVX-512 intrinsics for masks? 【发布时间】:2017-07-18 13:19:22 【问题描述】:

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

KSHIFT左/右 KADD KTEST

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

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

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

【问题讨论】:

请注意,屏蔽指令只能在 Skylake-avx512 的一个 ALU 端口上运行。我不确定哪个端口,但它是与向量指令冲突的端口之一。 (kmov 到/从整数寄存器可能也使用该端口,因此移动到整数并返回单个移位对于吞吐量来说仍然是愚蠢的,如果不是延迟的话)。 至少对于ktest/jcc,移动到整数寄存器而不是使用ktest 允许将test/jcc-march=skylake-AVX512 进行宏融合。对-march=knl 来说简直是愚蠢。 出于兴趣,为了需要额外的 KMOV,实现融合是否值得?也就是说,ktest+jcckmov+test/jcc? 这可能至少在前端问题吞吐量方面达到收支平衡,但对于代码大小来说更糟。 ktest + jcc 是 2 或 3 微秒。希望ktest 只是 1,但 SSE/AVX ptest 是 2 uop(1 用于测试,1 用于将结果从向量域移动到整数,与movd 相同的端口)。 kmov + test/jcc 几乎可以肯定总共只有 2 个微指令。 【参考方案1】:

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

但要了解为什么会这样,您需要查看 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 位。第二轮还添加了KADDKTEST,这在Knights Landing中是不存在的。

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


虽然我们不知道它们失踪的确切原因,但有一些强有力的证据支持它:

编译器/语法:

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

性能不一致:

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

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

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


最后的想法:

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

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

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


更新:

目前尚不清楚这是什么时候发生的,但最新版本的英特尔内部函数指南有一组新的掩码内部函数,其中包含涵盖所有指令和宽度的新命名约定。这些新的内在函数取代了旧的。

所以这解决了整个问题。虽然编译器支持的程度仍然不确定。

例子:

_kadd_mask64() _kshiftri_mask32() _cvtmask16_u32() 取代 _mm512_mask2int()

【讨论】:

如果您只执行一条掩码指令,那么使用掩码指令会更便宜。在这些情况下,我偶尔可以让 ICC 生成它们。但是,如果您正在执行KADDKSHIFTKUNPACK 之类的操作,那么您可能正在执行多个掩码指令。在往返 GPR 变得更便宜之前不需要太多时间。还要注意,掩码指令只有 1 个/周期的吞吐量,而 GPR 整数指令通常是 2-4 个/周期。 就编译器不生成最佳序列而言,AVX512 仍然是新的,优化器在它们方面仍然不成熟。所以最后,如果你想要完全控制,你需要内联汇编。即便如此,ICC 中的某些错误也会降低它的用处。 @Mysticial:如果有机会,您可以通过检查与在已知端口上运行的其他指令的资源冲突来检查端口而无需性能计数器。例如使用 shuffle + kshift 吞吐量检查 p5。 p1 与imul + kshift 吞吐量。 p0 与 movd eax, xmm0pmovmskb + kshift 吞吐量。 (或者我猜想用 512b 指令关闭 p1 以进行向量操作,很多东西只在 p0 上运行,比如 pmullw。) @PeterCordes 看起来有人打败了阿格纳:github.com/InstLatx64/InstLatx64/blob/master/… @PeterCordes 最新版本的英特尔内在函数指南有一组新的掩码内在函数,涵盖了所有内容。它有一个取代旧的命名约定!

以上是关于缺少掩码的 AVX-512 内在函数?的主要内容,如果未能解决你的问题,请参考以下文章

avx512中比较内在指令的不同语义?

AVX512 缺少内在的 _mm512_round_ps

使用内在函数将双 SSE2/AVX/AVX512 存储为浮点数的最佳方法

给定一个 int 偏移向量,如何使用 AVX512 内在函数收集单个字节?

发行版将 GCC 升级到 5.5.0 后,AVX512 内在函数头会产生许多错误

AVX512BW:使用bsf / tzcnt处理32位代码中的64位掩码?