簡體   English   中英

Constexpr 和 SSE 內在函數

[英]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包裝器更好的主要原因之一。)


GCC解決方案。 (不適用於clang或MSVC)。

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.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

有趣的是:在禁用優化的情況下,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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM