c++ - 如何混合 32 位整数?或 : Why is there no _mm256_blendv_epi32?

标签 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)
        ) 
    );
}

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

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

是否有一些边境案例会惨遭失败?例如,当整数位模式恰好代表一个浮点 NAN 时会发生什么? Blendv 是简单地忽略这一点还是会发出一些信号?

如果这有效:我是否正确地认为有一个 8 位、一个 32 位和一个 64 位的 blendv 但缺少 16 位的 blendv?

最佳答案

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

My code relies on blendv only checking the highest bit.



那么你有两个不错的选择:
  • 使用算术右移 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);
    }
    

    但是, Intel SnB 系列 CPU 在将整数结果转发到 FP 混合单元时具有 1 个周期的旁路延迟延迟 ,在将混合结果转发到其他整数指令时还有 1c 延迟。如果延迟不是瓶颈,这可能不会影响吞吐量。

    有关旁路延迟延迟的更多信息,请参阅 Agner Fog's microach guide 。这就是他们不为 FP 指令制作 __m256i 内在函数的原因,反之亦然。请注意,自 Sandybridge 以来,FP shuffle 没有额外的延迟来转发/转发 PADDD 等指令。因此,如果 PUNPCK* 或 PALIGNR 不完全符合您的要求,那么 SHUFPS 是组合来自两个整数 vector 的数据的好方法。 (整数上的 SHUFPS 即使在 Nehalem 上也是值得的,如果吞吐量是你的瓶颈,它在两种情况下都有 2c 的惩罚)。

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

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

    但是,如果您将其用作长依赖链的一部分,那么可能值得使用额外的指令来避免混合数据的额外 2 个延迟周期。

    请注意,如果掩码生成在关键路径上,则 VPSRAD 的 1 个周期延迟等效于旁路延迟延迟,因此使用 FP 混合对于掩码->结果链仅增加 1 个周期的延迟,而 2数据->结果链的额外周期。

    For example, what happens when the integer bit pattern happens to represent a floating point NAN?



    BLENDVPS 不在乎。 Intel 的 insn ref manual fully documents everything an instruction can/can't do SIMD 浮点异常 : None 表示这不是问题。另请参阅 标记 wiki 以获取文档链接。

    FP blend/shuffle/bitwise-boolean/load/store 指令不关心 NaN。只有进行实际 FP 数学运算的指令(包括 CMPPS、MINPS 和类似的东西)才会引发 FP 异常或者可能会因非规范而减慢速度。

    Am I correct that there is a 8-bit, a 32-bit and a 64-bit blendv but a 16-bit blendv is missing?



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

    最常见的用例是比较掩码,其中每个元素都已经是全 1 或全零,因此您可以与 PAND/PANDN => POR 混合。当然,只保留掩码的符号位和真值的巧妙技巧可以节省指令和延迟,特别是因为可变混合比三个 bool 位指令要快一些。 (例如 ORPS 两个浮点 vector 以查看它们是否都是非负数,而不是 2x CMPPS 和 ORing 掩码。如果您不关心负零,或者您乐于将下溢处理为 -0.0,这可以很好地工作为阴性)。

    关于c++ - 如何混合 32 位整数?或 : Why is there no _mm256_blendv_epi32?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40746656/

    相关文章:

    c - x86_64 SSE 对齐 : differences between GCC and Clang

    c++ - 我是否违反了严格的别名规则?

    c++ - 将内联数学库与预编译头文件结合使用时出现链接错误

    c++ - 如何在远程类之间共享数据?

    c++ - C/C++ 语法 - 使用 , 而不是 ; 分隔语句合法的?

    c++ - 使用 Direct2D 和 DirectWrite(C++、DirectX)制作按钮

    c++ - 如何删除由 union 成员及其隐式删除的成员函数引起的代码重复?

    c - 使用简单预处理器定义的内存映射地址

    c - 如何使用从c中的文件读取的文本打开文件

    c - 使用getrandom在C中随机 float