[英]Does software prefetching allocate a Line Fill Buffer (LFB)?
我已經意識到利特爾定律限制了在給定延遲和給定並發級別下傳輸數據的速度。 如果你想更快地傳輸一些東西,你要么需要更大的傳輸,更多的“飛行中”傳輸,或者更低的延遲。 對於從 RAM 讀取的情況,並發性受到 Line Fill Buffers 數量的限制。
當加載未命中 L1 緩存時,會分配一個行填充緩沖區。 現代英特爾芯片(Nehalem、Sandy Bridge、Ivy Bridge、Haswell)每個內核有 10 個 LFB,因此每個內核僅限於 10 個未完成的緩存未命中。 如果 RAM 延遲為 70 ns(合理),並且每次傳輸為 128 字節(64B 緩存線加上其硬件預取孿生),這會將每個內核的帶寬限制為:10 * 128B / 75 ns = ~16 GB/s。 單線程Stream等基准測試證實這是相當准確的。
減少延遲的明顯方法是使用 x64 指令(例如 PREFETCHT0、PREFETCHT1、PREFETCHT2 或 PREFETCHNTA)預取所需數據,這樣就不必從 RAM 中讀取數據。 但是我無法通過使用它們來加速任何事情。 問題似乎是 __mm_prefetch() 指令本身消耗了 LFB,因此它們也受到相同的限制。 硬件預取不會觸及 LFB,也不會跨越頁面邊界。
但是我在任何地方都找不到任何記錄。 我發現的最接近的是 15 年前的文章,其中提到 Pentium III 上的預取使用行填充緩沖區。 我擔心從那時起事情可能會發生變化。 由於我認為 LFB 與 L1 緩存相關聯,我不確定為什么預取到 L2 或 L3 會消耗它們。 然而,我測量的速度與這種情況是一致的。
那么:有沒有什么方法可以在不使用這 10 個行填充緩沖區之一的情況下從內存中的新位置啟動提取,從而通過繞過利特爾定律獲得更高的帶寬?
根據我的測試,所有類型的預取指令都會消耗最近的 Intel 主流 CPU 上的行填充緩沖區。
特別是, 我在 uarch-bench 中添加了一些加載和預取測試,它在各種大小的緩沖區上使用大跨度加載。 以下是我的 Skylake i7-6700HQ 的典型結果:
Benchmark Cycles Nanos
16-KiB parallel loads 0.50 0.19
16-KiB parallel prefetcht0 0.50 0.19
16-KiB parallel prefetcht1 1.15 0.44
16-KiB parallel prefetcht2 1.24 0.48
16-KiB parallel prefetchtnta 0.50 0.19
32-KiB parallel loads 0.50 0.19
32-KiB parallel prefetcht0 0.50 0.19
32-KiB parallel prefetcht1 1.28 0.49
32-KiB parallel prefetcht2 1.28 0.49
32-KiB parallel prefetchtnta 0.50 0.19
128-KiB parallel loads 1.00 0.39
128-KiB parallel prefetcht0 2.00 0.77
128-KiB parallel prefetcht1 1.31 0.50
128-KiB parallel prefetcht2 1.31 0.50
128-KiB parallel prefetchtnta 4.10 1.58
256-KiB parallel loads 1.00 0.39
256-KiB parallel prefetcht0 2.00 0.77
256-KiB parallel prefetcht1 1.31 0.50
256-KiB parallel prefetcht2 1.31 0.50
256-KiB parallel prefetchtnta 4.10 1.58
512-KiB parallel loads 4.09 1.58
512-KiB parallel prefetcht0 4.12 1.59
512-KiB parallel prefetcht1 3.80 1.46
512-KiB parallel prefetcht2 3.80 1.46
512-KiB parallel prefetchtnta 4.10 1.58
2048-KiB parallel loads 4.09 1.58
2048-KiB parallel prefetcht0 4.12 1.59
2048-KiB parallel prefetcht1 3.80 1.46
2048-KiB parallel prefetcht2 3.80 1.46
2048-KiB parallel prefetchtnta 16.54 6.38
需要注意的關鍵是,在任何緩沖區大小下,沒有一種預取技術比加載快得多。 如果任何預取指令不使用 LFB,我們希望它對於適合它預取到的緩存級別的基准測試來說非常快。 例如, prefetcht1
將行帶入 L2,因此對於 128-KiB 測試,如果它不使用 LFB,我們可能期望它比加載變體更快。
更確切地說,我們可以檢查l1d_pend_miss.fb_full
計數器,其描述為:
請求需要 FB(填充緩沖區)條目但沒有可用條目的次數。 請求包括可緩存/不可緩存的請求,即加載、存儲或軟件預取指令。
描述已經表明 SW 預取需要 LFB 條目並且測試證實了這一點:對於所有類型的預取,對於並發是限制因素的任何測試,這個數字都非常高。 例如,對於 512-KiB prefetcht1
測試:
Performance counter stats for './uarch-bench --test-name 512-KiB parallel prefetcht1':
38,345,242 branches
1,074,657,384 cycles
284,646,019 mem_inst_retired.all_loads
1,677,347,358 l1d_pend_miss.fb_full
fb_full
值大於周期數,這意味着 LFB 幾乎一直都是滿的(它可能大於周期數,因為每個周期可能需要一個 LFB 最多兩個負載)。 此工作負載是純預取,因此除了預取之外沒有任何東西可以填充 LFB。
此測試的結果也收縮了 Leeor 引用的手冊部分中聲稱的行為:
在某些情況下,預取不會執行數據預取。 這些包括:
- ...
- 如果內存子系統用完一級緩存和二級緩存之間的請求緩沖區。
顯然,這里的情況並非如此:當 LFB 填滿時,預取請求不會被丟棄,而是像正常加載一樣停止,直到資源可用(這不是不合理的行為:如果您要求軟件預取,您可能想要得到它,也許即使這意味着拖延)。
我們還注意到以下有趣的行為:
prefetcht1
和prefetcht2
之間存在一些小的差異,因為它們報告了 16-KiB 測試的不同性能(差異有所不同,但始終不同),但是如果您重復測試,您會發現這更有可能只是運行間變化,因為這些特定值有些不穩定(大多數其他值非常穩定)。prefetcht0
預取。 這有點奇怪,因為prefetcht0
應該與加載非常相似(並且在 L1 情況下每個周期可以發出 2 個)。12 / 10 == 1.2
個周期d 期望(最佳情況)如果 LFB 是限制性事實(並且fb_full
非常低的值證實了這一點)。 這可能是因為 12 個周期的延遲是一直到執行核心的完整加載到使用延遲,其中還包括幾個周期的額外延遲(例如,L1 延遲是 4-5 個周期),所以實際花費在LFB 小於 10 個周期。prefetcht1
和prefetcht2
始終比 load 或prefetcht0
快 0.3 個周期。 給定 10 個 LFB,這等於減少了 3 個周期的占用,或多或少地由預取在 L2 處停止而不是一直到 L1 來解釋。prefetchtnta
通常比 L1 之外的其他吞吐量低得多。 這可能意味着prefetchtnta
實際上正在做它應該做的事情,並且似乎將行帶入 L1,而不是 L2,而只是“弱”地進入 L3。 因此,對於包含 L2 的測試,它具有並發限制的吞吐量,就好像它正在擊中 L3 緩存一樣,而對於 2048-KiB 的情況(L3 緩存大小的 1/3),它具有擊中主內存的性能。 prefetchnta
限制了 L3 緩存污染(每組只有一種方式) ,所以我們似乎正在被驅逐。這是我在測試之前寫的一個較舊的答案,推測它是如何工作的:
一般來說,我希望任何導致數據在 L1 中結束的預取都會消耗一個行填充緩沖區,因為我相信 L1 和其余內存層次結構之間的唯一路徑是 LFB 1 。 因此,針對 L1 的 SW 和 HW 預取可能都使用 LFB。
然而,這留下了目標 L2 或更高級別的預取不消耗 LFB 的可能性。 對於硬件預取的情況,我很確定情況確實如此:您可以找到許多參考資料,說明硬件預取是一種有效地獲得更多內存並行性的機制,超出 LFB 提供的最大值 10。 此外,L2 預取器似乎無法根據需要使用 LFB:它們位於 L2 中/附近並向更高級別發出請求,大概使用超級隊列並且不需要 LFB。
這留下了針對 L2(或更高級別)的軟件預取,例如prefetcht1
和prefetcht2
2 。 與 L2 生成的請求不同,這些請求從內核開始,因此它們需要某種方式從內核發出,這可能是通過 LFB。 從英特爾優化指南中有以下有趣的引用(強調我的):
一般來說,軟件預取到 L2 會比 L1 預取顯示更多的好處。 到 L1 的軟件預取將消耗關鍵硬件資源(填充緩沖區),直到緩存行填充完成。 到 L2 的軟件預取不保存這些資源,並且不太可能對性能產生負面影響。 如果您確實使用 L1 軟件預取,最好是通過 L2 緩存中的命中來服務軟件預取,這樣可以最大限度地縮短保留硬件資源的時間。
這似乎表明軟件預取不消耗 LFB - 但此引用僅適用於 Knights Landing 架構,我找不到任何更主流架構的類似語言。 看來 Knights Landing 的緩存設計明顯不同(或者引用錯誤)。
1事實上,我認為即使是非臨時存儲也使用 LFB 來離開執行核心——但它們的占用時間很短,因為一旦到達 L2,它們就可以進入超隊列(實際上並沒有進入 L2 ) 然后釋放它們關聯的 LFB。
2我認為這兩個都針對最近的 Intel 上的 L2,但這也不清楚 - 也許t2
提示實際上針對某些 uarch 上的 LLC?
首先是一個小修正——閱讀優化指南,你會注意到一些硬件預取器屬於 L2 緩存,因此不受填充緩沖區數量的限制,而是受 L2 對應物的限制。
“空間預取器”(你所說的 colocated-64B 線,完成 128B 塊)就是其中之一,所以理論上,如果你每隔一行讀取一次,你將能夠獲得更高的帶寬(一些 DCU 預取器可能會嘗試“為您填補空白”,但理論上他們應該具有較低的優先級,因此它可能會起作用)。
然而,“國王”預取器是另一個人,“L2 流光”。 第 2.1.5.4 節內容如下:
Streamer:此預取器監視來自 L1 緩存的讀取請求,以獲取地址的升序和降序序列。 受監控的讀取請求包括由加載和存儲操作以及硬件預取器發起的 L1 DCache 請求,以及用於代碼提取的 L1 ICache 請求。 當檢測到前向或后向請求流時,預取預期的緩存行。 預取緩存行必須在同一個 4K 頁面中
重要的部分是——
流送器可能會在每次 L2 查找時發出兩個預取請求。 拖纜最多可以在加載請求之前運行 20 行
這個 2:1 的比率意味着對於被這個預取器識別的訪問流,它總是在您的訪問之前運行。 確實,您不會自動在 L1 中看到這些行,但這確實意味着如果一切正常,您應該始終為它們獲得 L2 命中延遲(一旦預取流有足夠的時間提前運行並減輕 L3/內存延遲)。 您可能只有 10 個 LFB,但正如您在計算中所指出的 - 訪問延遲越短,您更換它們的速度就越快,您可以達到的帶寬就越高。 這實質上是將L1 <-- mem
延遲分離到L1 <-- L2
和L2 <-- mem
並行流中。
至於您標題中的問題 - 嘗試填充 L1 的預取將需要一個行填充緩沖區來保存該級別檢索到的數據,這是合情合理的。 這可能應該包括所有 L1 預取。 至於 SW 預取,第 7.4.3 節說:
在某些情況下,預取不會執行數據預取。 這些包括:
- PREFETCH 導致 DTLB(數據轉換后備緩沖區)未命中。 這適用於 CPUID 簽名對應於系列 15、模型 0、1 或 2 的奔騰 4 處理器。 PREFETCH 解決 DTLB 未命中並在 CPUID 簽名對應於系列 15、模型 3 的奔騰 4 處理器上獲取數據。
- 對導致故障/異常的指定地址的訪問。
- 如果內存子系統用完一級緩存和二級緩存之間的請求緩沖區。
...
所以我假設你是對的,軟件預取不是一種人為增加未完成請求數量的方法。 但是,同樣的解釋也適用於此處 - 如果您知道如何使用軟件預取提前足夠好地訪問您的線路,您可能能夠減輕一些訪問延遲並增加您的有效 BW。 然而,這不適用於長流,原因有兩個:1)您的緩存容量有限(即使預取是臨時的,如 t0 風格),以及 2) 您仍然需要支付完整的 L1-->mem 延遲每次預取,因此您只是將壓力向前移動了一點 - 如果您的數據操作比內存訪問快,您最終會趕上您的軟件預取。 所以這只有在你可以提前足夠好地預取所有你需要的東西時才有效,並將其保存在那里。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.