為什麼 x86‑TSO 模擬在 ARM 上是效能瓶頸,以及 FEX 如何解決它
核心問題:在弱序 ARM ISA 上模擬 x86‑TSO
在 ARM 的弱序模型上模擬 x86 Total Store Ordering (TSO) 記憶體模型會產生巨大的效能損失,因為每個 x86 載入指令都必須轉換為 ARM 的 load‑acquire,而每個儲存指令都必須轉換為 store‑release。 這些指令比一般的載入/儲存指令昂貴得多,且原本並非設計為主要的指令類別。
為什麼 x86‑TSO 對應用程式很重要
x86‑TSO 保證在任何後續載入指令觀察到之前,儲存指令對所有核心都是可見的,這讓程式設計師無需額外的記憶體屏障即可進行記憶體推理。 這種嚴格的一致性內建於 x86 ISA 中,幾乎所有的 PC 遊戲和許多舊版應用程式都依賴於此。
ARM 的弱一致性模型
ARM 預設的載入和儲存並非立即一致;儲存指令可能會保留在私有快取行中,直到被明確清除,而載入指令可能會看到過時的資料。 為了獲得順序性,ARM 提供了 load‑acquire 和 store‑release 指令(即「RCsc」模型),它們充當輕量級的屏障。
初始模擬策略:到處使用 Acquire/Release
FEX 最初將每個 x86 載入映射為 ARM 的 load‑acquire,將每個儲存映射為 store‑release,這在功能上是正確的,但代價極高。 在五款 CPU 上的微基準測試顯示,acquire‑loads 的速度下降了高達 50 %,而多個核心(例如 AmpereOne)上的 release‑stores 吞吐量則大幅降低。
分裂鎖 (Split‑Lock) 的挑戰
x86 程式經常執行跨越快取行邊界的未對齊存取(「分裂鎖」),這些存取在 x86 上是原子的,但在 ARM 上會導致對齊錯誤。 ARM 要求 acquire/release 指令必須自然對齊;未對齊的存取會觸發錯誤,迫使 FEX 在執行時期修補 JIT、插入資料記憶體屏障 (DMB) 並重試。
"當這些修補點發生對齊錯誤時,FEX 會捕捉該錯誤,將程式碼從 load‑acquire/store‑release 指令修補為基本的等效載入‑儲存指令,並將該指令封裝在資料記憶體屏障中。" – FEX 文章
錯誤處理路徑涉及每個分裂鎖的內核到使用者空間的往返,這可能比原生執行慢數千倍。
有幫助的 ARM 擴充功能
LRCPC (FEAT_LRCPC, LRCPC2, LRCPC3)
LRCPC 系列增加了「Release Consistency processor‑consistent」載入指令,這些指令符合 x86‑TSO 語義,且沒有沉重的 acquire‑load 懲罰。 基準測試顯示,LRCPC‑loads 在大多數 CPU 上達到了接近基準的吞吐量。
FEAT_LSE2
FEAT_LSE2 將 acquire/LRCPC/Release 指令的對齊要求放寬至 16 位元組粒度,但在跨越 64 位元組(快取行)時仍然會出錯。 這對分裂鎖僅有微小的增益,因為大多數 x86 原子操作跨越了整個 64 位元組的快取行。
Apple 的硬體 TSO 模式 – 理想的解決方案
Apple Silicon 實作了一個硬體開關,使一般的 ARM 載入/儲存指令遵循 x86‑TSO 語義,消除了對 acquire/release 或 LRCPC 指令的需求。 在 M1 上,對齊和未對齊存取的效能幾乎相同,唯一的開銷是在啟用 TSO 模式時,儲存指令會有約 5 % 的輕微減速。
"Apple 的硬體直接增加了對 x86‑TSO 記憶體模型的支援。當此 CPU 功能被切換時,他們一般的 ARM 載入/儲存指令會改變行為以符合 x86 的要求。" – FEX 文章
當 FEX 偵測到此功能(例如透過 Asahi Linux)時,它可以啟用硬體 TSO 模式並獲得相同的「免費」效能提升。
原子 RMW 指令:將 x86 LOCK 映射到 ARM
ARMv8.1‑a 為所有 18 個 x86 LOCK 前綴的 RMW 操作提供了對應的等效指令(例如 LOCK ADD → ldaddal)。 然而,同樣的對齊錯誤問題依然存在:跨越快取行的未對齊原子 RMW 仍然需要昂貴的錯誤處理路徑。
未快取 (Write‑Combine) 記憶體 – 一個致命問題
當遊戲使用 Vulkan 的 VK_MEMORY_HOST_CACHED_BIT 清除(寫入合併緩衝區)時,ARM 的 LRCPC 載入速度會大幅變慢,而儲存速度可能比 x86 慢 816 倍。 這是因為未快取記憶體迫使每個載入都必須存取系統記憶體,而 LRCPC 增加了額外的屏障。
"這其中最糟糕的情況是儲存效能有多差,與 Zen 在儲存上的效能相比,這基本上是一個致命問題。頻寬差了高達 816 倍!" – FEX 文章
FEX 在 UMA 系統上透過強制驅動程式使用快取緩衝區來減輕此問題,但在 PCIe‑GPU 系統上,該問題仍未解決。
各廠商的測試結果
| CPU | 對齊載入/儲存 | 未對齊懲罰 (載入) | 未對齊懲罰 (儲存) |
|---|---|---|---|
| AmpereOne | 基準 ≈ 28 GB/s 儲存 | ≈ 0 % (載入) | ~8.5 % 減速 (儲存) |
| Cortex‑X4 | 11.5 GB/s 載入, 6.7 GB/s 儲存 | ~50 % 減速 (兩者) | |
| Cortex‑X925 | 與 X4 相似,絕對頻寬更高 | ||
| Oryon‑3 | 對齊 LRCPC‑load 符合基準;儲存 ≈ 基準的 68 %;未對齊儲存 ≈ 43 % | ||
| Apple M1 | 儲存減速 < 5 %;載入不變 – 硬體 TSO 消除了對齊開銷 |
針對分裂鎖的未來硬體支援建議
一個可行的硬體修復方案是 128 位元的 CASP,它可以在不引發對齊錯誤的情況下,原子地跨越 64 位元組邊界進行操作。 該操作可以失敗並重試,利用 ARM 的 LL/SC 模型來保證向前推進。
給開發者與架構師的要點
- 在沒有硬體支援的情況下在 ARM 上模擬 x86‑TSO 代價高昂 – acquire/release 指令與分裂鎖的錯誤處理佔據了主要的開銷。
- LRCPC 擴充功能大幅改善了基準載入/儲存效能,但無法解決未對齊原子操作或寫入合併的情況。
- Apple 的硬體 TSO 模式展示了最佳路徑:一個單一開關即可讓一般載入/儲存指令遵循 x86‑TSO,從而獲得接近原生的效能。
- 未快取 (寫入合併) 記憶體仍然是 PCIe‑GPU 系統的關鍵瓶頸;驅動程式層級的變通方法是目前唯一實際的緩解措施。
- 未來的 ARM 擴充功能(例如支援分裂鎖的
CASP)可以縮小跨越快取行的原子操作的剩餘效能差距。
社群反應
"Apple 六年前就透過在 x86 模擬變得重要時,簡單地在晶片中增加一個 x86 相容的記憶體排序模式解決了這個問題。這是 Apple 晶片引領產業的又一證明。" – @modeless
"FEX 很棒;我遇到的大多數問題都與反作弊有關,但考慮到架構障礙,模擬器本身的執行速度令人印象深刻。" – @sureglymop
"這篇文章重複了常見的斷言,即 ARM 是最寬鬆的,而 x86 是最嚴格的;寬鬆的模型並不一定有太大的好處。" – @pdw (連結至外部討論)
結論
在 ARM 上模擬 x86‑TSO 從根本上來說很困難,因為這兩種 ISA 強制執行相反的記憶體排序保證;目前的最佳軟體方法(acquire/release → LRCPC → 硬體 TSO)在大多數工作負載上能產生可接受的效能,但分裂鎖和未快取記憶體仍然會導致嚴重的減速。 持續的 ARM ISA 擴充和更廣泛地採用硬體 TSO 模式(如 Apple 的模式)是未來在 ARM 平台上實現無縫、高效能 x86 模擬的最有希望的路徑。
Sources
相關
- Dispatch
- Dispatch
- Dispatch
- Dispatch
- Dispatch