←新闻详情

Chips and Cheese更新于 10月26日 00:22

实测 Windows 11 Prism 二进制翻译:x86-64 程序在 Arm 上的性能损失

On Binary Translation and its Consequences - by Chester Lam

AI 导读

Chips and Cheese 作者 Chester Lam 用 Geekbench 7 实测 Windows 11 的 Prism 二进制翻译,发现 x86-64 程序经翻译后执行的 aarch64 指令数约为原生两倍,各核心均出现数代性能损失。测试在 Snapdragon X2 Elite Extreme X2E-96-100 笔记本和 Azure 上的 Neoverse N1、N2 实例上进行,Snapdragon 的 E-Core 与 P-Core 即使承担翻译损失仍分别超过 Neoverse N1 和 N2。作者还查看 C:\Windows\XtaCache 中的翻译代码,指出 Prism 会做不必要的寄存器溢出,并把 256 位 AVX 运算拆成多条 128 位 NEON 指令。

推荐理由

AI 评分 52

作者用 Geekbench 7 实测 Windows 11 的 Prism 二进制翻译,量化了 x86-64 程序在 Arm 上的指令膨胀与性能损失。

主题

标签

正文 · AI 翻译

PC 市场是 CPU 设计者最难参与竞争的一个领域。PC 市场的消费者期望设备在各类应用中都能提供高性能,并且众所周知,他们期望自己的设备能够超越受限的、精心筛选过的用例集合。微软自家的 Surface RT 在十多年前就曾明显失败,原因正是它无法运行 PC 用户期望在 Windows 上直接就能用的那些程序。高通等厂商近期的 64 位 Arm 核心已经足够快,能够满足 PC 性能等式中的性能部分,但软件兼容性则构成了一道更难的障碍。PC 软件传统上是为 x86-64 构建的。要让开发者提供其程序的 64 位 Arm(aarch64)版本,是一个缓慢而渐进的过程。有些程序可能永远不会有 aarch64 移植版,因为它们已不再处于活跃开发中,而且只以二进制形式分发。因此,Arm 在 PC 市场的机会取决于微软能否确保 x86-64 二进制程序在 aarch64 主机上无缝运行。

Windows 11 最新的二进制翻译器名为 Prism,它通过将 x86-64 指令翻译为 aarch64 指令来实现这一点。二进制翻译颇具挑战性,因为 x86-64 指令有时无法以直接的方式映射到 aarch64 指令。翻译还必须足够快,以尽量减少程序启动延迟,并避免为执行翻译而消耗过多 CPU 时间。所有这些都意味着,与运行等效的原生代码相比,二进制翻译会带来性能损失。

在这里,我用 Geekbench 7 来考察这种性能损失究竟有多大。Geekbench 7 同时提供 aarch64 和 x86-64 版本,这为比较二进制翻译下的性能与原生 aarch64 性能提供了机会。John Poole(Primate Labs 创始人、Geekbench 创作者)慷慨地提供了一把专业版密钥,让我能够对各个工作负载进行性能分析。当然,单一基准测试套件无法反映广泛应用程序中的行为,但我认为这是一个不错的起点。至于硬件,我测试使用的是华硕送测的 Asus Zenbook A16 笔记本电脑中的 Snapdragon X2 Elite Extreme X2E-96-100,以及 Microsoft Azure 上可用的 Arm 实例。Geekbench 7 指令特征

与 Geekbench 6 一样,Geekbench 7 包含若干在可用时利用向量扩展的工作负载。该套件偏向高 IPC、计算密集型工作负载。通过英特尔软件开发模拟器(SDE)并设置为暴露 Haswell 特性集来运行工作负载,结果显示超过一半的工作负载使用了 AVX 和 256 位向量宽度。我以 200 次迭代运行每个工作负载,以确保绝大多数被计数的指令来自工作负载本身,而不是 Geekbench 的测试框架。

在 200 次迭代下,每个工作负载大约执行数万亿条指令。来自各种 aarch64 核心的性能计数器数据显示,在执行 Geekbench 7 的原生 aarch64 二进制程序时,指令计数大致相似,偶有例外。高通的核心在许多测试中报告的退休指令数高于 Neoverse N1 和 N2,这有些奇怪,暗示硬件性能监控可能存在一些不准确之处。

在二进制翻译下执行 Geekbench 7 的 x86-64 版本,会导致整体指令计数大幅膨胀,PDF Viewer 除外。作为经验法则,一个 Geekbench 7 工作负载经过二进制翻译时,执行的 aarch64 指令数大约是其 x86-64 指令数的两倍。

微软暗示,二进制翻译在所有 aarch64 CPU 上的工作方式并不相同。不同的 CPU 支持不同的指令集扩展,其中一些可能让 Prism 更紧密地将 x86-64 指令映射到 aarch64 指令。从理论上说,aarch64 CPU 还可以提供超出 ISA 要求的保证,例如即使 aarch64 允许一个核心以乱序于程序顺序的方式观察到另一个核心的存储,它仍可维持存储顺序。

Prism 专门针对高通 Snapdragon 处理器进行了优化和调校。Prism 中的一些性能特性需要仅在 Snapdragon X 系列中才有的硬件特性,但 Prism 在 Windows 11 24H2 上所有受支持的 Windows 11 on Arm 设备上均可用。

如果 Prism 是专门针对高通核心优化的,那么从指令计数方面看并没有区别。Ampere Altra 的 Neoverse N1 是我测试过的最老的核心,性能计数器显示执行的指令计数非常相似。

相似的指令计数当然可能掩盖巨大差异。性能最终才是关键,而这既受核心架构影响,也受所生成指令的性质影响。分数影响

Geekbench 7 的分数显示出二进制翻译带来的严重惩罚。包括高通核心在内的每个核心,都承受了相当于数代性能损失的影响。不过,Snapdragon X2 Elite 中的核心基线性能远高于 Neoverse N1 和 N2。Snapdragon X2 Elite 拥有一个由 6 个“性能”核心组成的集群,以及两个各由 6 个“prime”核心组成的集群。“性能”核心为 6 宽,运行频率为 3.6 GHz。由于它们在其他芯片中承担与能效核心相同的角色,为简便起见,我将称它们为 E-Core。“Prime”核心为 9 宽,运行频率为 5 GHz。它们以最大性能为目标,因此我将称它们为 P-Core。

即使承担二进制翻译惩罚,高通的 E-Core 仍能超过 Neoverse N1。P-Core 对 Neoverse N2 也是如此。面对以极高时钟速度运行的宽得多的核心,即使存在二进制翻译惩罚,也确实无可争辩。

高通核心从二进制翻译中承受的惩罚也略低。很难判断这是因为 Prism 中针对高通的特定优化,还是因为高通核心的吞吐量高得多。二进制翻译导致的指令数增加让我想起玩 Claude 的 C 编译器(CCC)的经历。CCC 的情况涉及在关键路径之外生成大量额外指令,而乱序 CPU 通常可以吸收这些指令。近来的高性能核心通常大部分核心宽度未被利用,因此额外的核心宽度可以在一定程度上缓解更高的指令数。但我不确定这里是否如此,因为高通的 4 宽 E-Core 承受的惩罚比 5 宽的 Neoverse N2 更轻。

在二进制翻译下运行时,各个工作负载会遭受不同程度的损失。Navigation 是一个低 IPC 工作负载,严重受限于分支预测和后端内存延迟,在二进制翻译下运行时承受的惩罚相对较低。在另一端,Video Player 是一个良好向量化的工作负载,利用了 AVX 指令。该测试中的分数差异绝对巨大。Photo Editor 和 Photo Library 也处于类似情况。

在 Arm 的 Neoverse N1 和 N2 上,二进制翻译下的工作负载特征基本保持不变。

核心吞吐量(IPC)

性能计数器显示,高通核心通过利用未使用的核心宽度,削减了二进制翻译开销。然而,与指令数开销相比,IPC 提升幅度很小,这解释了巨大的二进制翻译惩罚。Snapdragon X2 Elite Extreme 的 P-Core 在 Video Player 工作负载中实现了巨大的 IPC 提升,但其他方面的增益往往较为有限。

高通的 E-Core 也处于类似情况,不过作为较窄的核心,它能更好地利用其核心宽度。在二进制翻译下运行时,Text Processing、HDR 和 Video Player 基本上让该核心达到其 4 宽宽度限制。然而,高通 P-Core 在 Text Processing 和 HDR 中仅见微弱的 IPC 提升,这表明加宽 E-Core 可能收效甚微,因为该工作负载可能很快会遇到其他限制。

Neoverse N2 起步时 IPC 较低,奇怪的是,在二进制翻译下运行 x86-64 时未能获得多少 IPC 提升。从原生工作负载的 IPC 来看,该核心在绝对意义上表现尚可,但与高通最新的能效优化核心相比就显得不太出色。无论是否处于二进制翻译下,Neoverse N2 上没有任何工作负载的平均 IPC 能超过 3。

Neoverse N1 是这组中最老的核心,不出所料它最为吃力。Ampere Altra 中的 N1 引领了 Arm 进军服务器市场的努力,并取得成功,因为该核心能提供足够的性能,同时比同时代的 x86-64 产品更注重密度优化。但那是多年前的事了。高通的 E-Core 也是 4 宽,却将其远远甩在身后。

高通 E-Core 相对良好的表现提醒我们,与其他架构特性相比,核心宽度相对而言并不那么重要。高通为其现代 E-Core 配备了比 Neoverse N1 大得多的乱序引擎。这样做让核心能保持更多指令在途,减少短时延迟对单条指令的影响。自上而下分析

考虑分派阶段流水线吞吐量后,原生运行和二进制翻译下运行的工作负载呈现出大体相似的图景,这并不奇怪,因为它们应该在做同样的高层工作。在高通的 P-Core 上,最大的差异是更多被利用的流水线槽位,以及一定程度上变得不那么受前端限制。

高通较窄的 E-Core 在许多测试中使用了其核心宽度的很大一部分,尤其是在二进制翻译下运行的 x86-64 版本中。较窄的核心更容易供给,与更宽的 P-Core 相比,因前端原因损失的流水线槽位要少得多。

在 Azure 上做详细的性能监控更难,因为该平台不会透传按流水线槽位粒度统计的自顶向下计数器。不过,用周期级核算(STALL_FRONTEND 和 STALL_BACKEND)大致勾勒一下就能看出,Neoverse N2 因后端和前端原因损失的流水线槽位都多于高通的 E-Core。一方面,喂饱 5 宽流水线比 4 宽更难。另外,高通拥有更大的核心私有缓存,以及为其 E-Core 集群配备的 12 MB 大容量 L2,延迟为 21 个周期。Neoverse N2 缺少这 1 MB L2,使核心暴露在约 100 周期的 L3 延迟之下,任何核心都难以应对。二进制翻译在这方面改变不大。

后端停顿在 Neoverse N1 上问题更大,它还必须应对超过 100 周期的 L2 未命中延迟。N1 受影响更严重,因为其乱序引擎更小。

与高通 E-Core 的对比显示出 Neoverse N1 的处境有多艰难。高通的核心设计为可保持两倍以上的指令在途,尽管其核心宽度相同、执行资源相近。二进制翻译带来的额外指令增加了对乱序资源的压力,而高通更强壮的核心有更好的机会吸收这部分开销。

翻译后的代码

Prism 将翻译后的代码存储在 C:\Windows\XtaCache 中。将生成的代码写入磁盘有两个作用。第一,它让 Prism 避免每次启动二进制文件时重新翻译代码的开销。第二,它让 Windows 用户能够检查翻译后的代码,以理解 x86-64 二进制文件如何能在 aarch64 主机上运行。这一点很重要,因为它能让用户对硬件产生共情。XtaCache 在后一点上的效果一直有限,因为微软忘了记录其 .jc 缓存格式,但幸好其他人已经介入补上了这一点。遗憾的是,翻查翻译后的代码仍然是一项非常耗时的练习。因此,这里我只给出几个例子,让大家对 Prism 的翻译有个概念。

Geekbench 7 的视频播放器工作负载之所以突出,是因为它在二进制翻译下运行时 IPC 高得多,同时也遭遇了执行指令数的巨大增长。在 Skylake 上用 VTune 对视频播放器进行性能分析显示,最热的基本块是一个 17 条指令的循环。该循环遍历一个数组,并使用 AVX FMA3 指令执行融合乘加运算。

Prism 将其翻译为在 Neoverse N1 和 Snapdragon X2 Elite 上均为 69 条指令的序列。AVX 指令被映射到 NEON 指令上,而 NEON 只有 128 位向量宽度。高通最新的核心支持 SVE,但使用它很可能不会有太大差别,因为高通的核心以 128 位向量长度实现 SVE。而且,固定宽度的 x86-64 指令无论如何都无法很好地映射到 SVE 的可变向量长度上。

指令开销来自用多条 NEON 指令执行 256 位向量运算、复制 x86-64 更灵活的寻址模式,以及不得不用单独的指令执行内存访问,而不是用一条指令同时指定加载和数学运算。例如,vbroadcastss ymm8, [r8+r10*4] 加载一个单精度标量(32 位)值并将其广播到所有向量通道。Prism 使用三条 aarch64 指令。一条加载该值,另一条将其复制到一个 128 位 NEON 寄存器的各通道,最后一条将该值复制到另一个代表 ymm8 高 128 位的 NEON 寄存器。

融合乘加指令的展开更多。vfmadd231ps ymm7, ymm8, [rax + r9*4 - 0x13c] 使用了 x86-64 最复杂的寻址模式,包含基址、比例索引和立即数偏移。Prism 用两条指令完成地址计算,并能将索引缩放和加到数组基址这两步合并为一条带缩放的加法。然后,一条加载对(LDP)指令指定将 256 位加载到一对 128 位 NEON 寄存器中。Prism 用一对 FMLA 指令执行 FMA 运算,但奇怪的是,它把保存 ymm7 高半部分的 NEON 寄存器溢出到栈上。它对循环中全部八条 FMA 指令都这样做,使缓存带宽需求翻倍。这些被溢出的寄存器在下一次循环迭代之前都没有被修改,因此溢出和重新加载它们完全没有必要,而且在一个本已带宽需求很高的循环中可能有害。在循环体接近末尾处,Prism 忠实地将软件预取指令翻译为 aarch64 的对应指令,包括生成地址的额外 ALU 指令。然后,循环计数器递增和控制流指令直接翻译为 aarch64 的对应指令,当然跳转目标地址不同。

人工翻译者会避免 Prism 不必要的寄存器溢出。他们还可以省略预取指令,因为线性遍历数组会形成可预测的访问模式,硬件预取器很可能会捕捉到。他们还可以在这里那里消除几条指令,比如用一个寄存器同时保存 ymm8 的两半,因为广播加载的目标从未被修改。不过 Prism 值得称赞,因为它识别出 rax + r9*4 已经生成,从而通过只发出一条减法,简化了八条 FMA 运算中六条的地址生成。

我还看了一下 Geekbench 7 的 HDR 工作负载,因为它的二进制翻译版本几乎没获得任何 IPC 提升。在 Skylake 上分析得到的最热基本块是一个包含标量 AVX 运算的 36 条指令循环。这应该更容易翻译,因为 Prism 不必将 AVX 更宽的向量宽度映射到 NEON 上。

Prism 将其翻译为超过 60 条指令。当我从我的 Neoverse N1 虚拟机中抓取时,完整的翻译似乎并未出现在翻译缓存中。

Prism 试图维持一种寄存器映射,让 x86 SSE 寄存器映射到编号相同的 aarch64 NEON 寄存器,这引入了可观的指令开销。例如,在指令序列中,Geekbench 7 执行一次浮点加法(vaddss xmm5, xxm4, [rdi+rbx*4])并将结果存储到内存(vmovss [rdi+rbx*4], xmm5)。Prism 的 aarch64 翻译将浮点加法结果放入 s18,然后在存储之前将其移动到 v5,以便用 v5 代表 xmm5。额外的开销还来自莫名其妙的栈存储,以及看似为右移指令生成进位标志的额外指令。许多 x86-64 指令会设置标志位,而并非所有指令在 aarch64 中都有设置标志位的等价指令。Prism 似乎更倾向于保留生成标志位的行为,而不是耗费计算能力去判断这些标志位是否被使用。

Prism 为高通的 Snapdragon X2 Elite 生成的序列大体相同,但在加载和存储方面存在差异。高通较新的核心支持 FEAT_LPCPC2,这使其可以使用 ldapurb(带立即数偏移的加载-获取)而不是 ldaprb(加载-获取)。能够采用立即数偏移简化了地址生成,省去了一些额外指令。在两种情况下,Prism 都将加载翻译为加载-获取指令,而不是 aarch64 的基本 LDR,以确保内存顺序。

在 Snapdragon X2 Elite 上生成的 Prism 代码结语

二进制翻译是让 Arm on Windows 在 PC 市场可行的关键。Prism 的翻译大多直截了当。x86-64 指令的行为由等价的 aarch64 序列忠实复现,以至于可以透过翻译后的代码轻松看出哪些序列对应 x86-64 指令。我怀疑 Prism 这样做是为了避免消耗过多计算能力,因为它所做的看起来可以通过对每个块进行线性遍历并在内存中保留最少状态来实现。然而,以如此直截了当的方式进行翻译,错过了人类可以实施的潜在优化。在 aarch64 中复现 x86-64 寻址通常需要额外指令。x86-64 编译器还利用将加载和数学运算结合的指令。这些指令在 aarch64 中必须拆分为两条指令,并且可能需要使用额外的临时寄存器来保存加载的值。

在硬件方面,高性能核心通常可以用未使用的核心宽度吸收死代码。当 Prism 生成死代码时,有时会出现这种情况,但 Prism 不会像 Claude 的 C 编译器那样生成过多的死代码。复现地址生成或在寄存器之间复制值以维持 Prism 的寄存器映射所带来的指令开销,很容易拉长关键依赖链。乱序引擎对此无能为力。

二进制翻译后果的终极答案,是首先拥有一个高性能且弱点很少的架构。高通似乎正通过其最新的 Snapdragon X2 Elite 朝着这个方向努力。一个运行在 5 GHz 的 9 宽核心不容小觑。大容量指令缓存(E-Core 上 128 KB,P-Core 上 192 KB)和充足的乱序资源总体上有利于性能,并且关键是有助于缓解二进制翻译带来的指令开销。我仍在 Asus Zenbook A16 UX3607OA 笔记本电脑上尝试不同的使用场景,但除了游戏等少数边缘场景外,使用起来感觉出乎意料地正常。我认为,随着更多应用获得 aarch64 移植,Windows on Arm 的情况只会随着时间推移变得更好,而更高性能的核心意味着即使应用必须通过二进制翻译运行,性能也会变得更加可接受。

关于此帖的讨论还有 5 条评论...

没有帖子

准备好了解更多了吗?

来源

Chips and Cheese