[英]Intel store instructions on delibrately overlapping memory regions
我必須將 YMM 寄存器中較低的 3 個雙精度存儲到大小為 3 的未對齊雙精度數組中(即,不能寫入第 4 個元素)。 但是有點頑皮,我想知道 AVX 內在的_mm256_storeu2_m128d
可以做到這一點。 我有
reg = _mm256_permute4x64_pd(reg, 0b10010100); // [0 1 1 2]
_mm256_storeu2_m128d(vec, vec + 1, reg);
並由 clang 編譯給出
vmovupd xmmword ptr [rsi + 8], xmm1 # reg in ymm1 after perm
vextractf128 xmmword ptr [rsi], ymm0, 1
如果storeu2
具有像memcpy
這樣的語義,那么它肯定會觸發未定義的行為。 但是使用生成的指令,這會不會出現競爭條件(或其他潛在問題)?
也歡迎使用其他方式將 YMM 存儲到大小為 3 的數組中。
除了英特爾作為文檔發布的內容之外,並沒有真正針對英特爾的內在函數 AFAIK 的正式規范。 例如他們的內在指南。 還有他們白皮書中的例子等等; 例如,需要工作的示例是 GCC/clang 知道他們必須使用__attribute__((may_alias))
定義__m128
一種方式。
這一切都在一個線程中,完全同步,所以絕對沒有“競爭條件”。 在您的情況下,商店發生的順序甚至無關緊要(假設它們不與__m256d reg
對象本身重疊!這相當於重疊 memcpy 問題。)您正在做的事情可能就像兩個不確定地將memcpy排序到重疊的目的地:它們肯定以一種或另一種順序發生,編譯器可以選擇其中之一。
存儲順序的可觀察差異是性能:如果您想在不久之后進行 SIMD 重新加載,那么如果 16 字節重新加載從一個 16 字節存儲中獲取數據,而不是兩個存儲的重疊,則存儲轉發會更好地工作.
不過,一般來說,重疊存儲對性能來說很好; 存儲緩沖區將吸收它們。 但是,這意味着其中之一是未對齊的,並且跨越緩存線邊界會更昂貴。
然而,這一切都沒有實際意義: 英特爾的內在指南確實列出了該復合內在的“操作”部分:
手術
MEM[loaddr+127:loaddr] := a[127:0] MEM[hiaddr+127:hiaddr] := a[255:128]
所以它首先被嚴格定義為低地址存儲(第二個參數;我認為你把這個倒過來了)。
你的方式花費 1 車道交叉洗牌 + vmovups + vextractf128 [mem], ymm, 1
。 根據它的編譯方式,兩個商店都不能在 shuffle 之后啟動。 (雖然看起來 clang 可能避免了這個問題)。
在 Intel CPU 上, vextractf128 [mem], ymm, imm
的前端成本為 2 vextractf128 [mem], ymm, imm
,而不是微融合為一個。 (出於某種原因,在 Zen 上也有 2 個 uops。)
在 Zen 2 之前的 AMD CPU 上,車道交叉 shuffle 超過 1 _mm256_permute4x64_pd
,因此_mm256_permute4x64_pd
比必要的更昂貴。
您只想存儲輸入向量的低車道和高車道的低元素。 最便宜的 shuffle 是vextractf128 xmm, ymm, 1
- 1 uop / 1c 延遲(無論如何,它將 YMM 向量分成兩個 128 位的一半)。 它與英特爾上的任何其他過道洗牌一樣便宜。
你想讓編譯器做的 asm 大概就是這個,只需要 AVX1。 AVX2 對此沒有任何有用的說明。
vextractf128 xmm1, ymm0, 1 ; single uop everywhere
vmovupd [rdi], xmm0 ; single uop everywhere
vmovsd [rdi+2*8], xmm1 ; single uop everywhere
所以你想要這樣的東西,它應該可以有效地編譯。
_mm_store_pd(vec, _mm256_castpd256_pd128(reg)); // low half
__m128d hi = _mm256_extractf128_pd(reg, 1);
_mm_store_sd(vec+2, hi);
// or vec[2] = _mm_cvtsd_f64(hi);
vmovlps
( _mm_storel_pi
) 也可以使用,但是使用 AVX VEX 編碼它不會節省任何代碼大小,並且需要更多的轉換才能讓編譯器滿意。
不幸的是,沒有vpextractq [mem], ymm
,只有 XMM 源,所以沒有幫助。
正如評論中所討論的那樣,是的,您可以執行vmaskmovps
但不幸的是,它不如我們在所有 CPU 上希望的那樣高效。 直到 AVX512 使蒙面的負載/商店成為一等公民,最好洗牌並做 2 個商店。 或者填充你的數組/結構,這樣你至少可以暫時踩到后面的東西。
Zen 有 2- vmaskmovpd ymm
負載,但非常昂貴的vmaskmovpd
存儲(42 vmaskmovpd
,YMM 每 11 個周期 1 個)。 或者 Zen+ 和 Zen2 是 18 或 19 uop,6 周期吞吐量。 如果您完全關心 Zen,請避免使用vmaskmov
。
在 Intel Broadwell 及更早版本上,根據Agner 的 Fog測試, vmaskmov
存儲為 4 uop ,因此比我們從 shuffle + movups + movsd 獲得的融合域 uop 多 1 個。 但是,Haswell 和更高版本確實管理 1/clock 吞吐量,因此如果這是瓶頸,那么它會擊敗 2 個商店的 2 周期吞吐量。 對於 256 位存儲,SnB/IvB 當然需要 2 個周期,即使沒有屏蔽。
在 Skylake 上,vmaskmov mem, ymm, ymm
只有 3 個uops(Agner Fog 列出了 4 個,但他的電子表格是手工編輯的,而且以前是錯誤的。我認為可以安全地假設 uops.info 的自動化測試是正確的。這是有道理的; Skylake-client 與 Skylake-AVX512 的核心基本相同,只是沒有實際啟用 AVX512。因此他們可以通過將其解碼為掩碼寄存器(1 uop)+ 掩碼存儲(另外 2 uop 沒有微融合)來實現vmaskmovpd
.
因此,如果您只關心 Skylake 及更高版本,並且可以分攤將掩碼加載到向量寄存器(可重用於加載和存儲)的vmaskmovpd
, vmaskmovpd
實際上非常好。 前端成本相同,但后端更便宜:每個商店地址和商店數據 uop 僅 1 個,而不是 2 個獨立的商店。 請注意 Haswell 和更高版本上的 1/clock 吞吐量與執行 2 個獨立存儲的 2-cycle 吞吐量。
vmaskmovpd
甚至可以有效地存儲轉發到掩碼重新加載; 我認為英特爾在他們的優化手冊中提到了一些關於這一點的內容。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.