LEA 与 MOV imm64 将地址常量加载到寄存器中

问题描述 投票:0回答:2

我有一个常量(64 位)地址,我想将其加载到寄存器中。该地址位于代码段中,因此可以相对于RIP 进行寻址。有什么区别

movabs rax, 0x123456789abc

lea rax, [rip+0xFF] // relative offset for 0x123456789abc

就执行速度而言,哪一个更可取(在理论上可以使用两种替代方案的情况下;例如在 JIT 中或当地址可以在链接时固定时)?通过查看反汇编结果,lea 的代码更少,但会因此更快吗?或者由于相对编码偏移可能会变慢?

assembly x86-64 micro-optimization
2个回答
2
投票

TL;DR: 在热循环中,前者 (

movabs
) 通常更快,因为它在大多数现代处理器上具有更高的倒数吞吐量。


事实上,在 Intel Haswell/Broadwell/Skylake/CoffeeLake/CannonLake/IceLake/TigerLake/RocketLake(这些湖太多)上,

movabs
的吞吐量倒数为 0.25,而
lea
的倒数吞吐量为 1(由于
rip
相对寻址)。

在最近的Intel AlderLake混合架构上,事情要复杂得多。 AlderLake 的 P 核 (GoldenCove) 的

movabs
的吞吐量倒数为 0.2,
lea
的倒数吞吐量为 1(主要是由于
rip
再次相对寻址)。 AlderLake 的 E-core (Gracemont) 非常不同:
movabs
的倒数吞吐量为 0.33,而
lea
的倒数吞吐量为 0.25。这意味着最好使用的指令取决于线程的调度位置!这太疯狂了。更有趣的是:看起来 Goldmont/Tremont 在 SunnyCove/WillowCove 时已经有了带有 rip 相对寻址的快速
lea
。这是因为 P 核和 E 核的架构是为不同的目的而设计的(据我所知,类 Mon 的架构是为低功耗处理器设计的,而类 Cove 的架构是为桌面处理器设计的)。更不用说英特尔最初肯定没有计划在同一芯片中混合两种架构。

AMD Zen1/Zen2 上,

movabs
为 0.25,
lea
为 0.5,因此前者也更好。在 AMD Zen3/Zen4 上,两者的吞吐量倒数均为 0.25,因此它们在此架构上的速度相同。

话虽这么说,前者需要更多的空间,并且“解码”速度可能比后者慢,因此后者可能会更好地超越热循环。事实上,指令被解码为微指令一次,然后放入缓存中进行相对较短的循环,但解码通常是执行一次大型代码的瓶颈(没有热循环或非常大的循环,并且可能需要从RAM 或 L3)。


2
投票
https://uops.info/

和 Jérôme Richard 的回答)。另外,因为它与位置无关,与 movabs 不同,所以我们至少应该考虑这两个选项。

根据 Agner Fog 的测试,在 Sandybridge 系列上,立即数实际上具有超过 32 个有效位需要额外的时间从 uop 缓存中获取,并且在紧密打包到 uop 缓存中存在一些限制,因为它需要借用空间。 (
https://agner.org/optimize/microarchitecture.pdf#page=125

- 在 Sandybridge 部分:这可能在以后的 CPU 中发生了一些变化)。即使没有任何 uop 缓存惩罚,10 字节 x86 机器代码大小的成本通常仍然是需要避免的。

如果您需要在每个时钟周期多次将地址存入寄存器,请从循环外部设置的另一个寄存器进行复制,或从内存加载(使用 RIP 相对寻址模式或从堆栈空间)。
在进行函数调用的循环中,无论如何,这将是比 1/时钟更大的瓶颈。

当然,如果您的地址实际上位于虚拟地址空间的低 32 位中,请使用 5 字节

movabs

(类似于用于静态数据的 Linux 非 PIE 可执行文件,或者类似于 x32 ABI。对于 JIT,使用Linux 的

mov eax, 0x1234567
如果你想启用它。)

相关:
如何将函数或标签的地址加载到寄存器中
涵盖了选择的详细信息,但没有讨论 LEA 吞吐量权衡。

© www.soinside.com 2019 - 2024. All rights reserved.