为什么 x86‑TSO 模拟在 ARM 上是性能瓶颈,以及 FEX 如何解决它
核心问题:在弱序 ARM ISA 上模拟 x86‑TSO
在 ARM 的弱序模型上模拟 x86 总存储排序 (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‑load 的速度下降高达 50%,且在多个核心(例如 AmpereOne)上 release‑store 的吞吐量显著降低。
分裂锁 (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‑load 在大多数 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 测试结果
| 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