[英]Constexpr and SSE intrinsics
大多數 C++ 編譯器都支持帶有內部結構的 SIMD(SSE/AVX) 指令,例如
_mm_cmpeq_epi32
我的問題是這個函數沒有被標記為constexpr
,盡管“語義上”沒有理由讓這個函數不是constexpr
因為它是一個純函數。
有什么辦法可以編寫我自己的(例如) _mm_cmpeq_epi32
版本,它是constexpr
?
顯然,我希望該函數在運行時使用正確的 asm,我知道我可以使用constexpr
慢函數重新實現任何 SIMD 函數。
如果你想知道我為什么關心 SIMD 函數的constexpr
。 非 constexprness 具有傳染性,這意味着我的任何使用這些 SIMD 函數的函數都不能是constexpr
。
不幸的是,英特爾的內在函數沒有定義為constexpr
。
他們沒有理由沒有。 編譯器可以並且確實在編譯時評估它們,以進行恆定傳播和其他優化。 (這是內置函數/內在函數比單條指令的內聯asm包裝器更好的主要原因之一。)
ICC會對其進行編譯,但是當您嘗試將其用作constexpr __m128i
的初始化程序的一部分時會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都會發生相同的事情。我認為它通常可以優化
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
復制,因此使用在每個函數調用中檢查的保護變量都很糟糕。看看是否需要對變量進行靜態初始化。)
GCC的xmmintrin.h
和emmintrin.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
有趣的是:在禁用優化的情況下,gcc的標頭在某些情況下避免了內聯函數。 我猜這會導致更好的調試符號,因此您不必單步進入內聯函數的定義(當在GDB中使用stepi
在顯示TUI源窗口的優化代碼中使用時,確實會發生這種情況。)
現在在 c++20 中有一個跨平台的解決方案。 std::is_constant_evaluate 允許我們做到這一點。
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 語句。 使用 if constexpr 很誘人,但這將始終導致函數返回 true。 不用擔心,分支總是會被優化掉,因為 std::is_constant_evaluate 的值在編譯時總是已知的(即使它返回 false)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.