Constexpr and SSE intrinsics

Constexpr and SSE intrinsics

本文关键字:intrinsics SSE and Constexpr      更新时间:2023-10-16

大多数C++编译器都支持具有类似内部功能的 SIMD(SSE/AVX( 指令

_mm_cmpeq_epi32

我的问题是这个函数没有标记为constexpr,尽管"语义上"没有理由不constexpr这个函数,因为它是一个纯函数。

有什么方法可以编写我自己的版本(例如(constexpr_mm_cmpeq_epi32

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

如果您想知道我为什么关心 SIMDconstexpr功能。非连续性具有传染性,这意味着我使用这些 SIMD 函数的任何函数都不能constexpr

不幸的是,英特尔的内在函数没有被定义为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
}

在 Godbolt 编译器资源管理器上看到它,有两个测试调用器(一个带有变量,一个带有
constexpr __m128i v1 {0x100000000, 0x300000002};输入(。 有趣的是,ICC不会通过pcmpeqd_mm_cmpeq_epi32进行常量传播;它加载两个常量和用途和实际pcmpeqd,即使启用了优化。 同样的事情发生在有/没有constexpr的情况下。我认为它通常会优化

海湾合作委员会确实接受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复制,因此很糟糕,使用一个保护变量,该变量在每次函数调用时都会被检查以查看变量是否需要静态初始化。


GCC 的xmmintrin.hemmintrin.h根据原生向量运算符(如*(或__builtin_ia32函数来定义英特尔内部函数。 看起来他们更喜欢尽可能使用运算符,而不是(__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

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

现在在c++20 中有一个跨平台的解决方案。 std::is_constant_evaluated 允许我们做到这一点。

template<typename T>
constexpr auto add(T&& l, T&& r) noexcept
{
if (std::is_constant_evaluated())
slow_add(std::forward<T>(l), std::forward<T>(r));
else
_mm_add_pd(l.value, r.value);
}

请注意此处使用普通的 if 语句。如果 constexpr 使用很诱人,但这总是会导致函数返回 true。不用担心,分支将始终被优化出来,因为 std::is_constant_evaluated 的值在编译时总是已知的(即使它返回 false(。