簡體   English   中英

fC - 如何在函數外定義 SIMD 變量?

[英]fC - How can I define SIMD variable(s) outside of a function?

    const __m128i ___n = _mm_set_epi32( 0x80808080, 0x80808080, 0x80808080, 0x80808080 );
    const __m128i w___ = _mm_set_epi32( 0x80808080, 0x80808080, 0x80808080, 0x0f0e0d0c );
    const __m128i z___ = _mm_set_epi32( 0x80808080, 0x80808080, 0x80808080, 0x0b0a0908 );
    const __m128i zw__ = _mm_set_epi32( 0x80808080, 0x80808080, 0x0f0e0d0c, 0x0b0a0908 );
    const __m128i y___ = _mm_set_epi32( 0x80808080, 0x80808080, 0x80808080, 0x07060504 );
    const __m128i yw__ = _mm_set_epi32( 0x80808080, 0x80808080, 0x0f0e0d0c, 0x07060504 );
    const __m128i yz__ = _mm_set_epi32( 0x80808080, 0x80808080, 0x0b0a0908, 0x07060504 );
    const __m128i yzw_ = _mm_set_epi32( 0x80808080, 0x0f0e0d0c, 0x0b0a0908, 0x07060504 );
    const __m128i x___ = _mm_set_epi32( 0x80808080, 0x80808080, 0x80808080, 0x03020100 );
    const __m128i xw__ = _mm_set_epi32( 0x80808080, 0x80808080, 0x0f0e0d0c, 0x03020100 );
    const __m128i xz__ = _mm_set_epi32( 0x80808080, 0x80808080, 0x0b0a0908, 0x03020100 );
    const __m128i xzw_ = _mm_set_epi32( 0x80808080, 0x0f0e0d0c, 0x0b0a0908, 0x03020100 );
    const __m128i xy__ = _mm_set_epi32( 0x80808080, 0x80808080, 0x07060504, 0x03020100 );
    const __m128i xyw_ = _mm_set_epi32( 0x80808080, 0x0f0e0d0c, 0x07060504, 0x03020100 );
    const __m128i xyz_ = _mm_set_epi32( 0x80808080, 0x0b0a0908, 0x07060504, 0x03020100 );
    const __m128i xyzw = _mm_set_epi32( 0x0f0e0d0c, 0x0b0a0908, 0x07060504, 0x03020100 );
    const __m128i LUT[16] = { ___n, x___, y___, xy__, z___, xz__, yz__, xyz_, w___, xw__, yw__, xyw_, zw__, xzw_, yzw_, xyzw };

對於比較和左打包例程的 SSE/SSSE3 版本,我使用了一個類似於上面的查找表。 一組比較每秒發生多次 (60) 次,我希望它在內存中而不是每次都設置,並且范圍僅限於一個 .c 文件,但嘗試從具有或不具有靜態產量的函數中定義它錯誤:初始化元素不是常量。 對於每個“集合”。 為什么會發生這種情況,我該如何正確執行此操作?

編譯器報告初始化器元素不是常量,因為_mm_set_epi32是一個函數調用並且不滿足初始化器的“常量”要求。 此外,您定義的各種變量___n等不符合初始化LUT常量。

您可以使用以下命令定義數組:

const __v4su LUT[16] =
{
    { 0x80808080, 0x80808080, 0x80808080, 0x80808080 },
    { 0x03020100, 0x80808080, 0x80808080, 0x80808080 },
    { 0x07060504, 0x80808080, 0x80808080, 0x80808080 },
    { 0x03020100, 0x07060504, 0x80808080, 0x80808080 },
    { 0x0b0a0908, 0x80808080, 0x80808080, 0x80808080 },
    { 0x03020100, 0x0b0a0908, 0x80808080, 0x80808080 },
    { 0x07060504, 0x0b0a0908, 0x80808080, 0x80808080 },
    { 0x03020100, 0x07060504, 0x0b0a0908, 0x80808080 },
    { 0x0f0e0d0c, 0x80808080, 0x80808080, 0x80808080 },
    { 0x03020100, 0x0f0e0d0c, 0x80808080, 0x80808080 },
    { 0x07060504, 0x0f0e0d0c, 0x80808080, 0x80808080 },
    { 0x03020100, 0x07060504, 0x0f0e0d0c, 0x80808080 },
    { 0x0b0a0908, 0x0f0e0d0c, 0x80808080, 0x80808080 },
    { 0x03020100, 0x0b0a0908, 0x0f0e0d0c, 0x80808080 },
    { 0x07060504, 0x0b0a0908, 0x0f0e0d0c, 0x80808080 },
    { 0x03020100, 0x07060504, 0x0b0a0908, 0x0f0e0d0c },
};

由於類型從__m128i更改為__v4su ,您可能需要強制轉換才能使用它。 (向量運算就是為此而設計的。)

但是,在任何函數之外定義它和/或使用靜態存儲持續時間並不能確保它會在內存中。

帶有編譯時常量參數的_mm_set_epi32被優化為向量常量,通常從內存加載。 你不需要在這方面幫助編譯器,事實上,如果你嘗試的話,情況會更糟,因為編譯器在這方面出奇地糟糕。 如果您確實需要/想要幫助編譯器進行常量布局,請使用某種類型的數組,例如alignas(16) static const int32_t LUT[] = {...}; 並使用_mm_load_si128( (__m128i*)&LUT[i*4] )或類似的東西。

像全零或全 1 位這樣的簡單向量甚至可以在具有pxor xmm0,xmm0pcmpeqd xmm1, xmm1的寄存器中pxor xmm0,xmm0甚至比加載更有效,因此確保編譯器在優化函數時可以看到常量值是使用_mm_set*內部函數定義常量的一個很好的理由。

_mm_set_epi32視為字符串文字:編譯器_mm_set_epi32將它放在哪里,如果多個函數需要相同的向量常量,甚至會進行重復合並。 (與字符串文字不同,它是一個值,而不是衰減為指向存儲的指針的東西,因此只有部分類比有效。)


_mm_set*唯一的好地方是函數內部 當前的編譯器在有效處理__m128i類型的全局/ static變量方面很__m128i ,未能將其作為靜態初始值設定項處理,因此他們實際上在.rodata節中放置了一個匿名向量常量,並將運行時構造函數/初始值設定項函數復制到空間在靜態存儲中的命名變量的.bss中。

(在 C 中,這只能發生在函數內部的static __m128i ,這使得它需要一個保護變量。在 C++ 中,允許非常量全局初始化器,如int foo = bar(123);在全局范圍內,即使bar不是 constexpr。在 C 中,您會遇到遇到的錯誤。)

例如:

#include <immintrin.h>

__m128i foo() {
    return _mm_setr_epi32(1,2,3,4);
}

用 GCC11.2 -O3(在 Godbolt 上)編譯到這個匯編。 (clang 和 ICC,以及我認為 MSVC,對於以下所有代碼塊都是相似的。)

    # in .text
foo():
        movdqa  xmm0, XMMWORD PTR .LC0[rip]
        ret

    # in .rodata
.LC0:
        .quad   8589934593     # 0x200000001
        .quad   17179869187    # 0x400000003
__m128i bar(__m128i v) {
    return _mm_add_epi32(v, _mm_setr_epi32(1,2,3,4));
}
bar(long long __vector(2)):
        paddd   xmm0, XMMWORD PTR .LC1[rip]
        ret

        .set    .LC1,.LC0     # make LC1 a synonym for LC0
    # GCC noticed and merged at compile time, not leaving it for the linker.
__m128i retzero() {
    //return _mm_setzero_si128();
    return _mm_setr_epi32(0,0,0,0);  // optimizes the same
}

        pxor    xmm0, xmm0
        ret

但這里是你從 C++ 編譯器中得到的全局向量

__m128i globvec = _mm_setr_epi32(1,2,3,4);
_GLOBAL__sub_I_foo():                          # static initializer code
        movdqa  xmm0, XMMWORD PTR .LC0[rip]        # copy from anonymous .rodata
        movaps  XMMWORD PTR globvec[rip], xmm0     # to named .bss space
        ret
# in .bss
globvec:
        .zero   16

這就是 C 錯誤消息“拯救”你的原因; C 不允許非常量靜態初始值設定項(函數除外)。

對於函數內的static __m128i ,情況會更糟:您將獲得一個保護變量以確保非常量初始化程序僅在第一次調用時運行。


實際使用globvec代碼基本上沒問題,它將使用它作為內存源操作數或正常加載它,但是任何優化,例如基於某些元素具有通過某些操作進行常量傳播的已知值將是不可能的。 您還使用了兩倍的空間,盡管初始化程序數據在啟動期間只被觸及一次,因此不會對緩存占用空間產生影響。

或者:

constexpr _m128 _val = {0.5f,0.5f,0.5f,0.5f};

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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