如何混合 32 位整数?或者:为什么没有_mm256_blendv_epi32?

B.S*_*.S. 4 c c++ sse intrinsics avx2

我正在使用 AVX2 x86 256 位 SIMD 扩展。我想做一个 32 位整数组件 if-then-else 指令。在 Intel 文档中,此类指令称为 vblend。

Intel 内在指南包含函数 _mm256_blendv_epi8。这个功能几乎可以满足我的需要。唯一的问题是它适用于 8 位整数。不幸的是,文档中没有 _mm256_blendv_epi32 。我的第一个问题是:为什么这个功能不存在?我的第二个问题是:如何模仿?

经过一番搜索后,我发现 _mm256_blendv_ps 可以满足我对 32 位浮点的需求。此外,我还发现了转换函数 _mm256_castsi256_ps 和 _mm256_castps_si256,它们从整数转换为 32 位浮点数并返回。将这些放在一起给出:

inline __m256i _mm256_blendv_epi32 (__m256i a, __m256i b, __m256i mask){
    return _mm256_castps_si256( 
        _mm256_blendv_ps(
            _mm256_castsi256_ps(a),
            _mm256_castsi256_ps(b),
            _mm256_castsi256_ps(mask)
        ) 
    );
}
Run Code Online (Sandbox Code Playgroud)

虽然这看起来像 5 个函数,但其​​中 4 个只是美化的转换,还有一个直接映射到处理器指令上。因此,整个功能可以归结为一条处理器指令。

因此,真正尴尬的部分是,似乎有一个 32 位的 blendv,只是缺少相应的内在函数。

是否存在某种边界情况会导致惨败?例如,当整数位模式恰好表示浮点 NAN 时会发生什么?Blendv 是简单地忽略这一点还是会发出一些信号?

如果这有效:我是否正确,有 8 位、32 位和 64 位 Blendv,但缺少 16 位 Blendv?

Pet*_*des 6

如果您的mask整个 32 位元素已经全零/全一vpcmpgtd(如结果),请_mm256_blendv_epi8直接使用。

我的代码依赖于 Blendv 仅检查最高位

那么你有两个不错的选择:

  • 使用算术右移 31 来广播每个元素内的高位,以设置VPBLENDVB( _mm256_blendv_epi8)。即VPSRAD:mask=_mm256_srai_epi32(mask, 31)

    VPSRAD 在 Intel Haswell 上是 1-uop,用于端口 0。(Skylake 上的吞吐量更高:p01)。如果您的算法在端口 0 上遇到瓶颈(例如整数乘法和移位),那么这并不是很好。

  • 使用VBLENDVPS实现吞吐量与延迟之间的关系。您是对的,所有强制转换只是为了让编译器满意,并且 VBLENDVPS 将在一条指令中完全执行您想要的操作。

    static inline
    __m256i blendvps_si256(__m256i a, __m256i b, __m256i mask) {
        __m256 res = _mm256_blendv_ps(_mm256_castsi256_ps(a), _mm256_castsi256_ps(b), _mm256_castsi256_ps(mask));
        return _mm256_castps_si256(res);
    }
    
    Run Code Online (Sandbox Code Playgroud)

    然而, Intel SnB 系列 CPU在将整数结果转发到 FP 混合单元时具有 1 个周期的旁路延迟延迟,而在将混合结果转发到其他整数指令时又具有 1c 的延迟。如果这不是长依赖链(跨迭代)的一部分,那么保存 uops 可能会更好,让 OoO exec 隐藏额外的延迟。

有关旁路延迟延迟的更多信息,请参阅Agner Fog 的 microach 指南。这就是他们不__m256i为 FP 指令创建内在函数的原因,反之亦然。请注意,自 Sandybridge 以来,FP 洗牌在从/向 PADDD 等指令转发时不再有额外的延迟。因此,如果 PUNPCK* 或 PALIGNR 不能完全满足您的要求,SHUFPS 是组合两个整数向量数据的好方法。(即使在 Nehalem 上,整数上的 SHUFPS 也是值得的,如果吞吐量是你的瓶颈,它双向都会有 2c 的损失)。

尝试两种方法和基准。无论哪种方式都可能更好,具体取决于周围的代码。

与 uop 吞吐量/指令数相比,延迟可能并不重要。另请注意,如果您只是将结果存储到内存中,存储指令并不关心数据来自哪个域。

但是,如果您将其用作长依赖链的一部分,那么如果关键路径经过正在混合的数据而不是掩码,那么可能值得使用额外的指令来避免正在混合的数据产生额外的 2 个周期的延迟。

请注意,如果掩码生成位于关键路径上,则 VPSRAD 的 1 个周期延迟相当于旁路延迟延迟,因此使用 FP 混合对于掩码->结果链来说仅增加 1 个额外周期的延迟,而 2 个周期数据->结果链的额外周期。如果您使用可以从 FP 或整数混合有效转发的指令来使用混合结果,那么使用 FP 混合绝对是胜利,可以在相同的延迟下节省指令(及其 uop)。


例如,当整数位模式恰好表示浮点 NAN 时会发生什么?

BLENDVPS 不在乎。英特尔的insn 参考手册完整记录了指令可以/不能执行的所有操作,并且SIMD 浮点异常:无意味着这不是问题。另请参阅标签wiki以获取文档链接。

FP 混合/洗牌/按位布尔/加载/存储指令不关心 NaN。只有进行实际 FP 数学运算的指令(包括 CMPPS、MINPS 等)才会引发 FP 异常,或者可能因非正规化而减慢速度。


我是否正确,有 8 位、32 位和 64 位 Blendv,但缺少 16 位 Blendv?

是的。但有 32 位和 16 位算术移位,因此使用 8 位粒度混合最多需要一条额外指令。(没有 PSRAQ,因此 64 位整数的 blendv 通常最好使用 BLENDVPD 完成,除非掩码生成可能不在关键路径上和/或同一掩码将在关键路径上重复使用多次。)

最常见的用例是比较掩码,其中每个元素已经是全 1 或全 0,因此您可以与 PAND/PANDN => POR 混合。当然,仅将掩码的符号位保留为真值的巧妙技巧可以节省指令和延迟,特别是因为变量混合比三个布尔按位指令要快一些。(例如 ORPS 两个浮点向量来查看它们是否都是非负的,而不是 2x CMPPS 并对掩码进行“或”操作。如果您不关心负零,或者您乐意将下溢-0.0视为消极的)。