假設xmm0是第一個引數,這是我想要生成的那種代碼。
psrldq xmm0, 1
vpermq ymm0, ymm0, 4eh
ret
我用內在函式寫了這個。
__m256i f_alias(__m256i p) {
*(__m128i *)&p = _mm_bsrli_si128(*(__m128i *)&p, 1);
return _mm256_permute4x64_epi64(p, 0x4e);
}
這是 的結果clang,沒關系。
f_alias: #clang
vpsrldq xmm1, xmm0, 1
vperm2i128 ymm0, ymm0, ymm1, 33
ret
但是gcc會產生錯誤的代碼。
f_alias: #gcc
push rbp
vpsrldq xmm2, xmm0, 1
mov rbp, rsp
and rsp, -32
vmovdqa YMMWORD PTR [rsp-32], ymm0
vmovdqa XMMWORD PTR [rsp-32], xmm2
vpermq ymm0, YMMWORD PTR [rsp-32], 78
leave
ret
我嘗試了不同的版本。
__m256i f_insert(__m256i p) {
__m128i xp = _mm256_castsi256_si128(p);
xp = _mm_bsrli_si128(xp, 1);
p = _mm256_inserti128_si256(p, xp, 0);
return _mm256_permute4x64_epi64(p, 0x4e);
}
clang 產生相同的代碼。
f_insert: #clang
vpsrldq xmm1, xmm0, 1
vperm2i128 ymm0, ymm0, ymm1, 33
ret
但是gcc在翻譯內在函式時太直白了。
f_insert: #gcc
vpsrldq xmm1, xmm0, 1
vinserti128 ymm0, ymm0, xmm1, 0x0
vpermq ymm0, ymm0, 78
ret
用內在函式撰寫此操作的好方法是什么?如果可能的話,我想gcc制作好的代碼。clang
一些附帶問題。
PSRLDQ與 AVX 代碼混用不好嗎?VPSRLDQ像以前那樣使用會更好clang嗎?如果使用 沒有任何問題PSRLDQ,這似乎是一種更簡單的方法,因為它不會YMM像VEX版本那樣將部分歸零。F兩者兼有指令的目的是什么I,似乎無論如何都做同樣的作業,例如VINSERTI128/VINSERTF128或VPERMI128/VPERMF128?
uj5u.com熱心網友回復:
Skylake 上的最佳 asm 將使用舊版 SSE psrldq xmm0, 1,其效果是將向量的其余部分保持不變,作為資料依賴項進行處理。(在暫存器上,指令無論如何都會讀取,因為這不是movdqa什么)。但這在 Haswell 或 Ice Lake上將是災難性的,當任何 YMM 具有“臟”上半部分時,當舊版 SSE 指令寫入 XMM 暫存器時,這兩者都需要代價高昂地轉換到“已保存的上層”狀態。我不確定 Zen1 或 Zen2/3/4... 如何處理它。
在 Skylake 上幾乎一樣好,并且在其他任何地方都最佳,是復制和移位,然后vpblendd復制到原始的高半部分,因為您不需要在 128 位通道之間移動任何資料。(_mm256_permute4x64_epi64(p, 0x4e);在您的版本中,車道交換與您在標題中詢問的操作分開。如果這也是您想要的其他東西,那么繼續使用vperm2i128合并作為該車道交換的一部分。如果不是,這是一個錯誤。)
vpblendd比任何 shuffle 更高效,能夠在多個執行埠中的任何一個上運行,在 Intel CPU 上具有 1 個周期延遲。(像在主流 Intel 上的通道交叉洗牌vperm2i128是 1 uop / 3 個周期延遲,而在 AMD 和 Alder Lake 的 E 核心上則明顯更差 。https://uops.info/)相比之下,變數與向量混合控制通常更昂貴,但即時混合非常好。
是的,在某些 CPU 上使用 XMM ( __m128i) 移位會更有效,而不是移位兩半然后與原始部分混合。這將減少使用強制轉換內在函式的輸入,但如果編譯器沒有對其進行優化,那么您將在 Zen1 和 Alder Lake E-cores 上浪費 uop,其中每一半都vpsrldq ymm需要一個單獨的 uop。
__m256i rshift_lowhalf_by_1(__m256i v)
{
__m128i low = _mm256_castsi256_si128(v);
low = _mm_bsrli_si128(low, 1);
return _mm256_blend_epi32(v, _mm256_castsi128_si256(low), 0x0F);
}
gcc/clang使用 xmm byte-shift 和 YMM 將其編譯為書面(Godbolt)vpblendd。(Clang 翻轉立即數并使用相反的源暫存器,但相同的區別。)
vpblendd在 Zen1 上是 2 uops,因為它必須處理向量的兩半。對于特殊情況,例如保留整個向量的一半,解碼器不會立即查看。它仍然可以復制到單獨的目標,而不必就地覆寫任一源。出于類似的原因vinserti128,不幸的是,也是 2 微秒。(vextracti128在 Zen1 上只有 1 個 uop;我希望vinserti128只有 1 個,并在檢查 uops.info 之前撰寫了以下版本):
// don't use on any CPU *except* Zen1, or an Alder Lake pinned to an E-core.
__m256i rshift_alder_lake_e(__m256i v)
{
__m128i low = _mm256_castsi256_si128(v);
low = _mm_bsrli_si128(low, 1);
return _mm256_inserti128_si256(v, low, 0); // still 2 uops on Zen1 or Alder Lake, same as vpblendd
// clang optimizes this to vpblendd even with -march=znver1. That's good for most uarches, break-even for Zen1, so that's fine.
}
Alder Lake E-cores 可能有一個小的好處,其中延遲vinserti128被列為[1;2]而不是. 但是由于任何 Alder Lake 系統也將具有 P 內核,因此您實際上并不想使用它,因為它在其他所有方面都更糟糕。2vpblenddvinserti128
同時擁有 VINSERTI128/VPERMI128 和 VINSERTF128/VPERMF128 的目的是什么?
vinserti128使用記憶體源僅執行 128 位加載,vperm2i128執行 256 位加載,這可能會跨越快取行或頁面邊界以獲取您甚至不會使用的資料。
在加載/存盤執行單元只有 128 位寬的資料路徑快取(如 Sandy/Ivy Bridge)的 AVX CPU 上,這是一個顯著的優勢。
在 shuffle 單元只有 128 位寬的 CPU 上(如本答案中討論的 Zen1),vperm2i128的 2 個完整源輸入和任意shuffle使它變得更加昂貴(除非我猜你有更智能的解碼器,可以發出許多 uops根據立即數移動向量的一半)。
例如,Zen1vperm2i/f128是 8 uop ,具有 2c 延遲,3c 吞吐量!。(Zen2 的 256 位執行單元將其提高到 1 uop、3c 延遲、1c 吞吐量)。見https://uops.info/
擁有 F 和 I 指令的目的是什么,似乎無論如何都做同樣的作業
與往常一樣(可以追溯到 SSE1orps與 SSE2 pxor/ 之類的東西orpd),讓 CPU 對于 SIMD-integer 與 SIMD-FP 具有不同的旁路轉發域。
Shuffle units are expensive so it's normally worth sharing them between FP and integer (and the way Intel does that these days results in no extra latency when you use vperm2f128 between vpaddd instructions).
But for example blend is simple so there probably are different FP and integer blend units, and there is a latency penalty for blendvps between paddd instructions. (See https://agner.org/optimize/)
uj5u.com熱心網友回復:
我當時很傻。clang給了我答案,為什么我沒有注意到?
vpsrldq xmm1, xmm0, 1
vperm2i128 ymm0, ymm0, ymm1, 33
ret
這個序列很簡單,
__m256i f_____(__m256i p) {
__m128i xp = _mm256_castsi256_si128(p);
xp = _mm_bsrli_si128(xp, 1);
__m256i _p = _mm256_castsi128_si256(xp);
return _mm256_permute2x128_si256(p, _p, 0x21);
}
并且確實gcc也產生了高效的代碼..
f_____:
vpsrldq xmm1, xmm0, 1
vperm2i128 ymm0, ymm0, ymm1, 33
ret
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/421103.html
標籤:
