假設我有 2 個長度為 64(或任何暫存器大小)的布爾陣列,我想將所有相應的布林值與結果第三個陣列相結合。顯然,可以將陣列打包到 2 個暫存器中并在一條指令中執行按位與,但如果需要進行位擺弄來打包和解包,這會慢得多。是否有執行打包的任何 x86 指令(或任何 x86 擴展集指令)?
uj5u.com熱心網友回復:
如果您希望能夠有效地做到這一點,您通常會一直保持陣列打包,并在 64 位暫存器中使用位索引訪問它們。例如,bt rdi, rax根據 RAX 索引的位數設定 CF。 bool CF = rdi & (1ULL<<(rax&63)).
不要使用bt或bts與記憶體目的地一起使用;它們具有瘋狂的 CISC 位串語意,bt [rdi], rax可以在 qword 之外進行[rdi]索引,如果目標不是暫存器,則使用整個 RAX 作為位索引。
如果您的陣列每個位元組存盤 1 個布林值,您通常只需使用兩條vpand指令一次按位與 32 個位元組 (AVX2)。 就像您對 256 位位圖進行與運算一樣,其中只有每 8 位可能為非零。
vmovdqu ymm0, [rdi] ; load 32 bytes
vpand ymm0, ymm0, [rsi] ; load and 32 bytes from the 2nd source
vmovdqu [rdx], ymm0 ; store 32 bytes
vmovdqu ymm0, [rdi 32] ; and repeat for the next 32 bytes.
vpand ymm0, ymm0, [rsi 32]
vmovdqu [rdx 32], ymm0
如果您for(int i=0;i<64;i ) c[i] = a[i]&b[i];為uint8_torbool元素撰寫代碼,編譯器應該會為您執行此操作。
使用 SSE2 或 AVX2 將布林值打包為位圖
但是如果你想將布林值打包成位圖,是的,pmovmskb這是你想要的特殊 x86 指令,將每個 SIMD 向量元素的最高位打包成一個整數。它自 SSE2 以來就存在,但 AVX2 相當廣泛可用,一次可以達到 32 個,而不僅僅是 16 個。
另請參閱如何從 8 個布林值中創建一個位元組(反之亦然)?為此,一次乘以 8 個位元組。
例如,使用 AVX2std::bitset<64>從 a制作 a:std::array<bool, 64>
vmovdqu ymm0, [rdi] ; first 32 bool elements
vpslld ymm0, ymm0, 7 ; shift the 0/1 to the top, 0x80 or 0x00 in each byte
vpmovmskb eax, ymm0
vmovdqu ymm0, [rdi 32]
vpslld ymm0, ymm0, 7
vpmovmskb edx, ymm0
vzeroupper ; if you might do any legacy SSE before next use of 256-bit vectors
shl rdx, 32 ; combine hi:lo halves
or rax, rdx ; ((uint64_t)hi << 32) | lo
# The 64 bits in RAX come from the bools in [rdi 0..63]
因此,這不僅僅是從兩個輸入一次 ANDing 32 個位元組。如果您想要來自兩個未打包輸入的打包結果,您可能想要_mm256_and_si256()它們然后 _mm256_slli_epi32/_mm256_movemask_epi8那些 AND 結果。
要再次解包,請參閱如何執行 _mm256_movemask_epi8 (VPMOVMSKB) 的逆運算?- 如果沒有 AVX-512,效率會降低。
使用 AVX-512
AVX-512 可以比較或測驗到一個掩碼暫存器,跳過這[v]pmovmskb一步。但是k0..7掩碼暫存器的用途有限(特別是如果您關心效率;kand只能在現有 CPU 的埠 5 上運行;https://uops.info/)。并且需要kmov從它們獲取資料到 RAX 等 GP 暫存器中。
例如內在函式:
#include <immintrin.h>
// or I could have declared these as taking bool *p args
__mmask64 foo(char *p){
__m512i v = _mm512_loadu_si512(p);
return _mm512_test_epi8_mask(v, v);
}
__mmask64 bar(char *p){
__m512i v = _mm512_loadu_si512(p);
return _mm512_cmpneq_epi8_mask(_mm512_setzero_si512(), v);
}
在 Godbolt 上編譯
# GCC12 -O3 -march=skylake-avx512
foo(char*):
vmovdqu64 zmm0, ZMMWORD PTR [rdi] # 64-byte load
vptestmb k0, zmm0, zmm0 # test into mask
kmovq rax, k0
vzeroupper # could have used ZMM16..31 to avoid this
ret
bar(char*):
vpxor xmm0, xmm0, xmm0
vpcmpb k0, zmm0, ZMMWORD PTR [rdi], 4
kmovq rax, k0
vzeroupper # not actually needed, this version doesn't write a ZMM register
ret
如果我使用了兩個不同的輸入陣列,我們可以用一條vptestmb指令將它們組合成一個位掩碼。所以最好還是這樣做,而不是單獨打包 a 的輸入kand k0, k1。
vmovdqu32 zmm0, [rdi]
vptestmb k1, zmm0, [rsi] ; k1 = packed bits of a[0..63] & b[0..63]
請參閱Skylake 是否需要 vzeroupper 以在僅讀取 ZMM 暫存器、寫入 ak 掩碼的 512 位指令后恢復渦輪時鐘?回復:當您通過 XMM 歸零隱式歸零后僅讀取 ZMM 暫存器時,是否需要 vzeroupper。無論哪種方式,編譯器都可以只使用 ZMM16..31 來避免接觸 y/zmm0..15 的上部。這將避免過渡停頓,即使程式的其余部分存在非零 ZMM 暫存器,AFAIK 也不會受到其他處罰。
如果您不在程式中的任何地方大量使用 512 位向量,則使用 512 位向量可能會降低性能,這就是編譯器默認-mprefer-vector-width=256使用自動向量化的原因。
- 降低 CPU 頻率的 SIMD 指令
- 為什么tigerlake的gcc自動矢量化使用ymm而不是zmm暫存器
如果您確實比較了兩個 32 位元組的一半,您可能希望kunpackdq k1, k1, k2在比較 k1 和 k2 之后,然后kmov rax, k1. 這連接了 k1 和 k2 的低 32 位。
開箱
AVX-512 最終添加了對將掩碼轉換為 0 / -1 元素的向量的直接支持,帶有vpmovm2b zmm0, k1( docs )。你可以vpandd用一個向量set1_epi8(1)來獲得布林值。
否則,請參閱
如何執行 _mm256_movemask_epi8 (VPMOVMSKB) 的逆運算?
intel avx2中的movemask指令是否有逆指令?- 元素大小和位數的各種組合
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/521682.html
標籤:Intel Collective 部件x86逻辑cpu架构硬件
上一篇:R中的匹配樣本
