2016-06-09 54 views
6

我在執行未對齊的加載或存儲在頁面邊界旁邊(例如使用_mm_loadu_si128/_mm_storeu_si128內部函數)之前,應先檢查整個向量(本例中爲16字節)屬於同一頁面,如果不是,則切換到非矢量指令。我明白,如果下一頁不屬於進程,則需要這樣做來防止coredump。SSE:未對齊的加載和跨越頁面邊界的存儲

但是,如果兩頁都屬於進程(例如它們是一個緩衝區的一部分,並且我知道該緩衝區的大小)呢?我寫了一個小的測試程序,它執行跨越頁面邊界的未對齊的加載和存儲,並沒有崩潰。在這種情況下,我是否必須始終檢查頁面邊界,還是足以確保我不會溢出緩衝區?

ENV:Linux中,x86_64的,GCC

回答

7

頁行分割都是不好的表現,但不影響未對齊訪問的正確性。 當您知道提前的時間長度時,確保您不會讀取緩衝區末尾的內容


爲了確保正確無誤,你經常需要像strlen,在那裏,當你發現一個標記值的循環停止執行的東西時,擔心它。該值可能位於矢量內的任何位置,因此只要執行16B未對齊的加載就可以讀取數組的末尾。如果終止0位於一頁的最後一個字節中,並且下一頁不可讀,並且當前位置指針未對齊,則包含0字節的負載也將包含來自不可讀頁面的字節,因此它將會出錯。

一個解決方案是做標量,直到你的指針對齊,然後加載對齊的向量。對齊的負載總是來自一個頁面,也來自一個緩存行。所以,即使你會讀取字符串末尾的一些字節,你也保證不會出錯。儘管如此,Valgrind可能會不高興,但標準庫strlen實現使用這個。

而不是標量,直到對準指針,你可以做從字符串的開始不對齊的矢量(只要不會跨頁行),然後做平衡負載。第一個對齊的負載將重疊第一個未對齊的負載,但對於像strlen這樣的函數來說,這並不在意它是否看到相同的數據兩次。


出於性能原因,可能值得避免頁面線拆分。即使您知道您的src指針未對齊,讓硬件處理緩存線分割通常也會更快。但在Skylake之前,頁面分割會有額外的〜100c延遲。 (Down to 5c in Skylake)。如果您有不同可以相互對齊多個指針,你不能總是隻用一個序幕對準你的src。 (例如c[i] = a[i] + b[i]c對準但b不是。)

在這種情況下,它可能會使用一個分支做對齊的加載與以前和頁面拆分後的價值,並與palignr將它們結合起來。

一個分支錯誤預測(〜15℃)比頁分裂延遲便宜,但會延遲一切(而不僅僅是負荷)。所以它可能也是值得的,這取決於硬件和計算的比例內存訪問。


如果你正在編寫一個函數,通常稱爲有排列的指針,是有意義的只是使用未對齊的加載/存儲指令。任何檢測錯位的序言對於已經對齊的情況來說都是額外的開銷,而在現代硬件(Nehalem和更新版本)上,在運行時對齊的地址上未對齊的加載與對齊的加載指令具有相同的性能。 (但你需要AVX作爲未對齊的加載,以摺疊爲其他指令作爲內存操作數,例如vpxor xmm0, xmm1, [rsi]

通過添加代碼來處理未對齊的輸入,可以減慢常見的對齊情況以加速罕見的未對齊情況。對未對齊的加載/存儲的快速硬件支持使得軟件可以在發生這種情況的少數情況下將其留給硬件。如果你使用的是AVX,順序32B AVX加載將緩存線分割所有其他負載。如果你使用的是AVX,那麼序列32B AVX加載將緩存行分割。 )

有關更多信息,請參閱Agner Fog's Optimizing Assembly guide以及標記wiki中的其他鏈接。

+0

@ZheyuanLi:是的,我很好奇哪些設計更改啓用了。 Skylake還可以並行執行兩個頁面散步來解決兩個TLB未命中。這兩個事實可能是相關的。 –

+0

謝謝!我也沒有意識到跨頁訪問可能會有這麼高的成本。所以這絕對是要尋找的東西。 –

+1

BTW,Valgrind有選項--partial-loads-ok = yes當加載的數據超過緩衝區末尾時,它可以隱藏由矢量加載引起的「無效讀」問題。 –