[英]AVX512: How to convert first 8 bytes into 8 64-bit integers?
我有一個__m512i
inputVector,其中 64 個字節中的每一個都包含一些偏移量。 接下來,我需要將前 8 個字節偏移量添加到存儲在另一個__m512i
變量( base
)中的 8 個 64 位值中。 (為了處理所有 64 字節的偏移量,我將下面的代碼重復 4 次)。 然而,在我可以對 8 個打包的 64 位整數進行向量加法之前,我需要將前 8 個字節轉換為 8 個 64 位整數。 目前我的代碼使用兩個_mm512_cvtepu*_epi*()
內在函數來實現這一點:
// Convert first 16 bytes from inputVector into 16 32-bit values
__m512i v16 = _mm512_cvtepu8_epi32(_mm512_extracti32x4_epi32(inputVector, 0));
// Convert first 8 32-bit values into 8 64-bit values
__m512i v8 = _mm512_cvtepu32_epi64(_mm512_extracti32x8_epi32(v16, 0));
// Finally do the addition
res = _mm512_add_epi64(base, v8);
有沒有更好的方法來實現這一目標? 我的解決方案感覺很笨拙,我對 AVX 沒有太多經驗。
_mm512_cvtepu8_epi64
對低 8 個字節完全符合您的要求。 它只查看輸入__m128i
的低 8 個字節; 你不需要做任何特別的事情來讓你的 8 個元素填充一個__m128i
。 https://felixcloutier.com/x86/pmovzx
您可以只_mm512_castsi512_si128
以防某些編譯器無法將extract(v,0)
優化為零 asm 指令。
對於更高的塊,您可以首先從 memory 加載 8 字節塊,這樣您就可以直接提供vpmovzxbq
指令,而不是加載 64 字節並且必須將高 qwords 洗牌到底部。
__m512i pmovzxbq(const char *p) {
// yes loadl takes a __m128i* pointer but only actually loads the low 8 bytes of it. Pretty poor API design in Intel's early SSE/SSE2 intrinsics
__m128i bytes = _mm_loadl_epi64((const __m128i*)p); // intrinsic for vmovq, but should optimize away into a memory source for pmovzx
__m512i qwords = _mm512_cvtepu8_epi64(bytes);
return qwords;
}
這與 GCC9 和 clang5.0 及更高版本( https://godbolt.org/z/vdxxGssoz )很好地編譯。 早期版本無法將負載折疊到 memory 源操作數中,執行單獨的vmovq
。 (盡管內存源版本無論如何都不會在當前 Intel 上使用 YMM 或更廣泛的目標對負載進行微融合。)
pmovzxbq:
vpmovzxbq zmm0, QWORD PTR [rdi]
ret
如果您確實想要進行更廣泛的負載,或者出於其他原因(例如某些計算的結果)在 SIMD 寄存器中包含壓縮字節,您有 2 個選項:
vpmovzxbq
的 8 字節塊隨機播放到底部第一個可以使用valignq
來右移/旋轉矢量以將您想要的部分放到底部。 (像_mm512_extracti32x4_epi32
這樣的即時控制洗牌只能在 16 字節的塊中工作;而像vpermq
這樣的一般洗牌只允許高達 YMM 的即時控制操作數,除此之外,只有一個帶有 ZMM 控制向量的版本。)
// normally worse than just doing 8-byte loads unless v is in a register already
__m512i chunk3 = _mm512_alignr_epi64(v,v, 3); // rotate right by 3 qwords
__m512i v3unpacked = _mm512_cvtepu8_epi64(_mm512_castsi512_si128(chunk3));
(GCC11 編譯為書面;clang13 悲觀到vextracti128
/ vpshufd
: https://godbolt.org/z/v1hfa7Ph3 )
這避免了需要加載任何常量或設置任何掩碼寄存器,並且valignq
在 Intel CPU 上只是具有 3c 延遲的單 uop,並且從 AVX-512F(即 Skylake-AVX512)開始支持。 https://uops.info/ 。
但這是循環內的額外指令,就像額外的加載 uop 一樣,但更糟糕的是,它會在端口 5 上與vpmovzxbq
競爭洗牌單元,除非周圍的代碼使用其他端口做大量工作,否則會損害吞吐量。
vpermb
這需要 Ice Lake 或更高版本的 AVX512 VBMI ( https://en.wikipedia.org/wiki/AVX-512#CPUs_with_AVX-512 )。 如果可用,您可以使用帶零掩碼的vpermb
將每個字節發送到需要的位置,並將所有 rest 歸零。 即從源向量的 8 字節塊中對字節進行零擴展,所有這些都具有一個字節混洗,在支持它的 CPU 上作為單個 uop 運行。
__m512i vin;
const __m512i shuf1 = _mm512_setr_epi64(8,9,10,11,12,13,14,15); // hopefully a compiler implements this with `vpmovzxbq` from memory, but probably will spend a full 64 bytes
const __m512i shuf2 = _mm512_add_epi64(shuf1, _mm512_set1_epi64(8)); // probably compilers will load each of these separately instead of generating on the fly...
const __m512i shuf3 = _mm512_add_epi64(shuf2, _mm512_set1_epi64(8));
const __mmask64 zextmask = 0x0101010101010101; // zero except for low byte of each qword.
// all the above outside a loop, or hopefully compilers can hoist them
__m512i v0unpacked = _mm512_cvtepu8_epi64(vin); // special case
__m512i v1unpacked = _mm512_maskz_permutexvar_epi8(zextmask, shuf1, vin);
__m512i v2unpacked = _mm512_maskz_permutexvar_epi8(zextmask, shuf2, vin);
...
(eg https://godbolt.org/z/czdjTeK41 - GCC loads 64-byte vector constants from memory, clang's shuffle optimizer turns it into memory-source vpmovzx
instructions like vpmovzxbq zmm2, qword ptr [rsi + rax + 16]
!)
因此,這在一個循環中很好,它證明了所有設置常量的工作是合理的,在循環內為每個 output 向量節省 1 個負載或 shuffle uop。
否則,一次只加載 8 個字節而不是 64 個字節,就像 clang 已經做的那樣,如果你在_mm512_loadu_si512
的結果上使用它。
根據您的評論,我能夠將我的代碼改進為:
__m128i bytes16 = _mm512_castsi512_si128(inputVector);
__m512i res = _mm512_cvtepu8_epi64(bytes16);
res = _mm512_add_epi64(base, res);
對於高 8 字節,它似乎稍微困難一些。 我嘗試的兩種方法都使用了比低 8 字節代碼更多的指令。 就性能而言,這兩種方法在我的測試中運行得同樣快:
第一個解決方案:
// Select bytes 8-15
__m512i high8Bytes = _mm512_maskz_compress_epi8(0x000000000000ff00ull, inputVector);
res = _mm512_cvtepu8_epi64(_mm512_castsi512_si128(high8Bytes));
res = _mm512_add_epi64(base, res);
第二個解決方案(我的原始代碼):
__m512i words32 = _mm512_cvtepu8_epi32(_mm512_castsi512_si128(inputVector));
__m512i res = _mm512_cvtepu32_epi64(_mm512_extracti32x8_epi32(words32, 1));
res = _mm512_add_epi64(base, res);
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.