Constexpr和SSE内在函数

NoS*_*tAl 9 c++ sse simd intrinsics constexpr

大多数C++编译器都支持SIMD(SSE/AVX)指令

_mm_cmpeq_epi32
Run Code Online (Sandbox Code Playgroud)

我的问题是这个函数没有标记为constexpr,虽然"语义上"没有理由不使用这个函数,constexpr因为它是一个纯函数.

有什么办法,我可以写我自己的版本(例如)_mm_cmpeq_epi32constexpr

显然我希望运行时的函数使用正确的asm,我知道我可以重新实现具有慢速函数的任何SIMD函数constexpr.

如果你想知道为什么我关心constexprSIMD功能.非constexprness具有传染性,这意味着我的任何使用SIMD功能的功能都不可能constexpr.

Pet*_*des 4

不幸的是,英特尔的内在函数没有定义为constexpr.

他们没有理由不能;编译器可以并且确实在编译时评估它们以进行常量传播和其他优化。(这是为什么内置函数/内在函数比单指令的内联 asm 包装器更好的主要原因之一。)


海湾合作委员会的解决方案。(不适用于 clang 或 MSVC)。

ICC 会编译它,但当您尝试将它用作constexpr __m128i.

constexpr
__m128i pcmpeqd(__m128i a, __m128i b) {
    return (v4si)a == (v4si)b;      // fine with gcc and ICC

    //return (__m128i)__builtin_ia32_pcmpeqd128((v4si)a, (v4si)b); // bad with ICC
    //return _mm_cmpeq_epi32(a,b);  // not constexpr-compatible
}
Run Code Online (Sandbox Code Playgroud)

在 Godbolt 编译器资源管理器上查看它,有两个测试调用程序(一个带有变量,一个带有
constexpr __m128i v1 {0x100000000, 0x300000002};输入)。有趣的是,ICCpcmpeqd通过or进行持续传播_mm_cmpeq_epi32;它加载两个常量并使用 和actualpcmpeqd,即使启用了优化。使用/不使用 constexpr 都会发生同样的事情。我认为它通常会优化

gcc 确实接受 constexpr __m128i vector_const { pcmpeqd(__m128i{0,0}, __m128i{-1,-1}) };


GCC(但不是 clang)将__builtin_ia32函数视为constexpr兼容的。GNU C x86 内置函数的文档 没有提到这一点,但可能只是因为它是 C 文档,而不是 C++。

GNU C 原生向量语法也是constexpr兼容的;这是第二种选择,只有当您不关心 MSVC 时才可行。

GNU C 定义__m128i为两个元素的向量long long。所以对于整数SIMD,你需要定义其他类型(或者使用gcc/clang/ICC的定义的类型)immintrin.h


(唯一奇怪的是,它static const __m128i foo = _mm_set1_epi32(2);不会变成常量初始值设定项;它.rodata在运行时复制,因此很糟糕,使用在每个函数调用时检查该变量是否需要静态初始化的保护变量。)


GCCxmmintrin.hemmintrin.h根据本机向量运算符(如*)或__builtin_ia32函数定义 Intel 内在函数。看起来他们更喜欢在可能的情况下使用运算符,而不是(__m128i)__builtin_ia32_pcmpeqd128((v4si)a, (v4si)b);

gcc 确实需要不同向量类型之间的显式转换。

来自 gcc7.3 emmintrin.h(SSE2):

extern __inline __m128i __attribute__((__gnu_inline__, __always_inline__, __artificial__))
_mm_cmpeq_epi32 (__m128i __A, __m128i __B)
{
  return (__m128i) ((__v4si)__A == (__v4si)__B);
}

#ifdef __OPTIMIZE__
extern __inline __m128i __attribute__((__gnu_inline__, __always_inline__, __artificial__))
_mm_shuffle_epi32 (__m128i __A, const int __mask)
{
  return (__m128i)__builtin_ia32_pshufd ((__v4si)__A, __mask);
}
#else
#define _mm_shuffle_epi32(A, N) \
  ((__m128i)__builtin_ia32_pshufd ((__v4si)(__m128i)(A), (int)(N)))
#endif
Run Code Online (Sandbox Code Playgroud)

有趣的是:如果在禁用优化的情况下进行编译,gcc 的标头在某些情况下会避免使用内联函数。我想这会带来更好的调试符号,因此您不必单步执行内联函数的定义(在stepi显示 TUI 源窗口的优化代码中在 GDB 中使用时确实会发生这种情况。)

  • 未优化的宏路径是因为某些指令需要立即常量参数,否则在 -O0 处获取会出现问题(需要内联函数,然后传播值)。 (3认同)