为什么在 ARM 上模拟 x86‑TSO 会成为性能瓶颈,以及厂商如何应对

TL;DR

在 ARM 的弱内存模型上模拟 x86‑TSO 代价高昂,因为每次加载/存储都必须转换为获取/释放指令,未对齐访问会触发代价高昂的对齐故障处理,而拆分锁原子操作需要重量级内核介入;最近的 ARM 扩展(LRCPC、FEAT_LSE2)以及 Apple 的硬件 TSO 模式缓解了大部分开销,使现代 ARM CPU 上的性能接近原生水平。


什么是 x86‑TSO 以及它为何重要?

  • x86‑TSO(总存储顺序) 保证一个存储操作在所有核心观察到任何后续加载之前就已可见。这为程序员提供了一个简单直观的模型:写入操作立即一致。
  • ARM 的弱(松弛)模型 允许存储操作保留在私有缓存行中,除非显式使用 获取/释放 指令,否则加载可能看到过时数据。
  • 当 x86 模拟器(例如 FEX)在 ARM 上运行时,必须将每个 x86 加载转换为 ARM 的 加载‑获取,并将每个 x86 存储转换为 ARM 的 存储‑释放,以保持 TSO 语义。
  • 这种转换比 ARM 原生模型严格得多,可能主导运行时性能。

使用获取/释放语义的基准性能

  • FEX 的早期实现对所有内存操作使用了 ARMv8.0‑a 的获取/加载和释放/存储。
  • 微基准测试显示,大多数 CPU 上性能下降了 2‑5 倍,因为获取/释放指令不能自由重排序,成为性能热点。
  • AmpereOne 服务器 CPU 的性能下降最严重:其释放/存储单元特别弱,且获取/加载的大量使用会拖慢流水线。
  • 即使是高端移动核心(Cortex‑X4、X925)在未对齐访问上也损失了约 50 %,这是由于对齐故障发生时需要额外的 DMB(数据内存屏障)。

未对齐访问与拆分锁

ARM 上的对齐故障

  • ARMv8.0 要求 自然对齐(地址 ≡ 大小)。违反此规则会引发 对齐故障,通常导致崩溃。
  • FEX 在每个加载/存储前插入一个 补丁点(NOP)。发生故障时,它将指令修补为包裹在 DMB 中的普通加载/存储,并恢复执行。
  • 这种回退机制有效,但每次未对齐访问都会产生一次内核到用户空间的往返,代价高昂。

x86 上的拆分锁

  • x86 允许 拆分锁 操作:跨越缓存行边界的原子读写。它们保持原子性和一致性,从不撕裂。
  • ARM 的原子指令仅对自然对齐访问是 单副本原子。FEAT_LSE2 扩展将其放宽到 16 字节粒度,但仍无法处理完整的 64 字节缓存行跨越。
  • 因此,FEX 必须通过两个 64 位比较并交换(CAS)操作序列以及内核中介的故障处理程序来模拟拆分锁。每次操作的开销高达 数百纳秒,远慢于原生 x86(约 1.4 ns)。

有助于缓解的架构扩展

LRCPC(释放一致性处理器一致)—— ARMv8.3+

  • 引入了 LRCPC‑加载 和 LRCPC‑存储 指令,实现与 x86‑TSO 要求匹配的内存模型。
  • 基准测试显示,LRCPC‑加载在所有测试 CPU(AmpereOne、Cortex‑X4/X925、Oryon‑3、Apple M1)上均达到 基准加载性能。
  • FEX 检测到 FEAT_LRCPC 特性后,从获取/加载切换为 LRCPC‑加载,消除了对齐访问的昂贵 DMB 路径。

FEAT_LSE2(大系统扩展 2)

  • 放宽了对获取/LRCPC/释放指令以及读-修改-写原子操作的对齐要求,允许 16 字节粒度内的任意地址。
  • 对 x86 模拟的提升 微乎其微,因为大多数 x86 代码访问的是完整的 64 字节缓存行(真正的拆分锁)。

Apple 的硬件 TSO 模式

  • Apple Silicon 实现了 线程级 TSO 切换(FEAT_TSO)。启用后,常规 ARM 加载/存储的行为如同 x86‑TSO。
  • 在 M1 上,LRCPC‑加载只是普通加载的别名,因此对齐和未对齐访问的开销均 ≤5 %。
  • 代价是原生 ARM 代码的性能略有下降(存储操作约 24 %),但影响微不足道,因为 FEX 的原生 ARM 执行占比极小。

厂商特定的性能快照

CPU 对齐加载(GB/s) 未对齐加载开销 对齐存储(GB/s) 未对齐存储开销
AmpereOne ~28(基准) ≈0 %(对齐处理) ~28 ~8.5 % 下降
Cortex‑X4 11.5 ~50 % 下降(DMB) 6.7 ~50 % 下降
Cortex‑X925 14.2 ~50 % 下降 9.1 ~50 % 下降
Oryon‑3 18.0(LRCPC‑加载 ≈ 基准) ~30 % 下降 12.2(≈68 % 基准) ~57 % 下降
Apple M1(TSO) ≈基准 ≤5 % ≈76 % 基准(仍很快) 可忽略
  • 数值来自文章中的微基准测试图表;为简洁起见省略了精确值。

未缓存(写合并)内存的挑战

  • 游戏经常通过 VK_MEMORY_HOST_CACHED_BIT = 0(未缓存、写合并)内存直接写入 GPU 缓冲区。
  • 在 ARM 上,未缓存存储仍使用 存储‑释放,无法从写合并缓冲中获益,导致带宽相比缓存存储 最多降低 816 倍。
  • 加载带宽损失更严重,因为每次加载都必须绕过缓存,直接访问系统内存。
  • 结果:在 PCIe‑GPU 系统上,模拟的 x86 游戏性能可能降至 <1 FPS(例如《空洞骑士:丝之歌》、《Subnautica 2》)。
  • 应对方案:
    • 在 UMA 系统(集成 GPU)上,强制驱动程序使用缓存缓冲区;NVIDIA Tegra、Snapdragon Adreno 和许多 Mali GPU 已经这样做了。
    • Apple 的 TSO 模式自动避免此问题,因为硬件将未缓存存储视为一致。
    • 目前没有 ARM 扩展能解决写合并存储问题;未来需要 FEAT_LRCPC4。

拆分锁模拟的正确性

  • FEX 当前的拆分锁模拟是 尽力而为:当非拆分锁原子仅修改 128 位区域的一半时,可能造成数据撕裂。
  • 一个简单的全局互斥锁方案会序列化所有拆分锁,但仍允许来自并发对齐原子操作的撕裂。
  • 由于游戏中拆分锁频率极高,采用全局“停止世界”方法不可行。
  • 作者提议使用一种 128 位 CASP,跨越缓存行边界但不引发对齐故障;操作可能失败并重试,从而保持原子性。
  • ARM 的事务内存扩展(TME)理论上可能有帮助,但已被弃用且从未发布。

社区见解

"Apple 六年前就通过在芯片中添加一个与 x86 兼容的内存排序模式解决了这个问题,当 x86 模拟变得重要时。这是 Apple 芯片领先行业的又一例证。" – modeless

"FEX 是一个 x86 到 ARM 的翻译框架,类似于 Apple 的 Rosetta2 和 Microsoft 的 Prism。Valve 赞助开发,因为它也是新 Steam Frame 支持 x86 游戏的方式。" – dagmx

"这篇文章重复了常见的说法,即 ARM 最松弛而 x86 最严格,但松弛模型并不一定有明显优势。" – pdw

这些评论强化了文章的核心观点:硬件级支持(Apple 的 TSO、ARM 的 LRCPC)是实现 ARM 上高性能 x86 模拟的唯一可行路径。


展望

  • ARM 厂商正逐步增加 LRCPC 和 LSE2 扩展,缩小与 x86‑TSO 的差距。
  • Apple 的硬件 TSO 模式表明,专用的 TSO 切换是最佳解决方案。
  • 未来的提案,如支持拆分锁的 128 位 CASP 或复兴的事务内存扩展,可能最终消除剩余的正确性和性能差距。
  • 在这些扩展普及之前,ARM 上的 x86 模拟器将继续偏好 UMA 平台,并依赖驱动程序补丁(例如 Valve 的 Steam Frame 内核补丁)来缓解未缓存内存的开销。

总结:在 ARM 上模拟 x86‑TSO 本质上代价高昂,因为两种内存模型处于一致性光谱的两端;然而,最近的 ARM ISA 扩展和 Apple 的硬件 TSO 模式显著降低了开销,使 ARM 上的高性能 x86 游戏越来越可行。

Sources

相关