簡體   English   中英

英特爾存儲故意重疊內存區域的指令

[英]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 及更高版本,並且可以分攤將掩碼加載到向量寄存器(可重用於加載和存儲)的vmaskmovpdvmaskmovpd實際上非常好。 前端成本相同,但后端更便宜:每個商店地址和商店數據 uop 僅 1 個,而不是 2 個獨立的商店。 請注意 Haswell 和更高版本上的 1/clock 吞吐量與執行 2 個獨立存儲的 2-cycle 吞吐量。

vmaskmovpd甚至可以有效地存儲轉發到掩碼重新加載; 我認為英特爾在他們的優化手冊中提到了一些關於這一點的內容。

暫無
暫無

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

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