簡體   English   中英

提取 __m128i 中每個布爾字節的低位? bool 數組到打包位圖

[英]Extract the low bit of each bool byte in a __m128i? bool array to packed bitmap

(編者注:這個問題最初是:如何訪問 __m128i 對象的 m128i_i8 成員或一般成員? ,試圖在 GCC 對__m128i的定義上使用 MSVC 特定的方法。但這是一個 XY 問題,並且接受的答案是關於這里的 XY 問題。另一個答案確實回答了這個問題。)

我意識到 Microsoft 建議不要直接訪問這些對象的成員,但我需要設置它們並且非常缺乏文檔

我繼續收到錯誤“請求成員 'm128i_i8' in '(my var name)',這是非類類型 'wirelabel {aka __vector(2) long long int}'”,我不明白,因為我'已經包含了所有正確的頭文件,它確實識別了 __m128i 變量。

注 1:線標是 __m128i 的 typedef,即存在於標頭中

typedef __m128i wirelabel 

Note2:使用 Note1 的原因在以下其他問題中進行了解釋: tbb::cache_aligned_allocator:使用 __m128i 獲取“對成員的請求...這是非類類型的”。 用戶錯誤或錯誤?

注3:我正在使用編譯器 g++

注4:以下問題沒有回答我的問題,但討論了相關信息為什么不直接訪問 __m128i 字段?

我也知道有一個 _mm_set_epi8 函數,但它要求您一次設置所有 8 位部分,目前這不是我的選擇。


接受的答案回答的問題:

編輯:我被問及為什么我認為我需要訪問__m128i對象的 16 個 8 位部分中的每一個的更多細節,原因如下:我有一個大小為 'n*128' 的bool數組(n 是size_t),我需要將它們存儲在大小為“n”的“wirelabel”數組中。

現在,因為wirelabel 只是__m128i 的別名/typedef(如果有差異,請糾正我),所以128 個布爾值的“n”個索引中的每一個都可以存儲在“wirelabel”數組中。

但是,為了做到這一點,我認為需要將每 8 位轉換為等價的有符號數,並將其存儲在數組中每個“wirelabel”指針的正確 8 位索引中。

所以你的源數據是連續的? 您應該使用_mm_load_si128而不是搞亂向量類型的標量組件。


您真正的問題是將bool數組(x86 上 g++ 使用的 ABI 中每個元素 1 個字節)打包到位圖中。 您應該使用 SIMD 執行此操作,而不是使用標量代碼一次設置 1 位或字節。

pmovmskb ( _mm_movemask_epi8 ) 非常適合每字節輸入提取一位。 您只需要安排將您想要的位放入高位即可。

顯而易見的選擇是移位,但向量移位指令在 Haswell(端口 0)上競爭與pmovmskb相同的執行端口。 http://agner.org/optimize/ )。 相反,添加0x7F將為1的輸入產生0x80 (高位設置),但為0的輸入產生0x7F (高位清除)。 (並且 x86-64 System V ABI 中的bool必須作為整數 0 或 1 存儲在內存中,而不僅僅是 0 與任何非零值)。

為什么不pcmpeqb反對_mm_set1_epi8(1) SKYLAKE微架構運行pcmpeqb上端口0/1,但paddb在所有3個矢量ALU端口(0/1/5)。 pmovmskb ,在pcmpeqb/w/d/q的結果上使用pmovmskb是很常見的。

#include <immintrin.h>
#include <stdint.h>

// n is the number of uint16_t dst elements
// We access n*16 bool elements from src.
void pack_bools(uint16_t *dst, const bool *src, size_t n)
{
     // you can later access dst with __m128i loads/stores

    __m128i carry_to_highbit = _mm_set1_epi8(0x7F);
    for (size_t i = 0 ; i < n ; i+=1) {
        __m128i boolvec = _mm_loadu_si128( (__m128i*)&src[i*16] );
        __m128i highbits = _mm_add_epi8(boolvec, carry_to_highbit);
        dst[i] = _mm_movemask_epi8(highbits);
    }
}

因為我們想在編寫這個位圖時使用標量存儲,所以出於嚴格別名的原因,我們希望dst位於uint16_t中。 使用 AVX2,您需要uint32_t (或者,如果您確實使用combine = tmp1 << 16 | tmp來組合兩個pmovmskb結果。但可能不要這樣做。)

這會編譯成這樣的 asm 循環( 使用 gcc7.3 -O3,在 Godbolt 編譯器資源管理器上

.L3:
    movdqu  xmm0, XMMWORD PTR [rsi]
    add     rsi, 16
    add     rdi, 2
    paddb   xmm0, xmm1
    pmovmskb        eax, xmm0
    mov     WORD PTR [rdi-2], ax
    cmp     rdx, rsi
    jne     .L3

所以這並不好(7 個熔斷域 uops -> 每 ~1.75 個時鍾周期 16 個布爾值的前端瓶頸)。 Clang 展開 2 次,每 1.5 個周期應管理 16 個布爾值。

使用移位 ( pslld xmm0, 7 ) 只會在 Haswell 上每 2 個周期運行一次迭代,在端口 0 上遇到瓶頸。

創建一個匿名聯合,其中包含_m128i成員和要設置其成員的其他類型的數組。 類型雙關在 C 中是合法的,並且支持作為 g++、clang++ 和 MSVC 的擴展。 如果要設置單個位,可以將另一個成員聲明為位域struct 位域的順序是實現定義的,但無論如何您都在使用英特爾內在函數,因此它將是小端的。

暫無
暫無

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

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