assembly 莱亚与MOV imm64用于将地址常数加载到寄存器

ycl3bljg  于 11个月前  发布在  其他
关注(0)|答案(2)|浏览(118)

我有一个恒定的(64位)地址,我想加载到寄存器中。这个地址位于代码段中,所以它可以相对于RIP进行寻址。

movabs rax, 0x123456789abc

字符串

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


在执行速度方面,哪一个更好(在理论上可以使用两种选择的情况下;比如在JIT中或者当地址可以在链接时固定时)?
通过查看反汇编,莱亚会导致更少的代码,但它会因此更快吗?或者由于相对编码偏移量而可能更慢?

7gs2gvoe

7gs2gvoe1#

**TL;DR:**在热循环中,前者(movabs)通常更快,因为它在大多数现代处理器上具有更高的吞吐量倒数。

事实上,在IntelHaswell/Broadwell/Skylake/CoffeeLake/CannonLake/IceLake/TigerLake/RocketLake(这些湖太多了)上,movabs的吞吐量倒数为0.25,而lea的吞吐量倒数为1(由于rip-相对寻址)。
在最近的英特尔AlderLake混合架构上,事情要复杂得多。AlderLake的P核(GoldenCove)的吞吐量倒数为movabs为0.2,lea为1(主要是由于rip-再次相对寻址)。AlderLake的E-core(Gracemont)有很大的不同:movabs的吞吐量倒数是0.33,而lea是0.25。这意味着最好的指令使用是依赖于线程被调度的位置!太疯狂了。更好笑的是:看起来Goldmont/特雷蒙已经有了快速的lea和RIP相对寻址,而SunnyCove/WillowCove。这是因为P核和E核的架构是为不同的目的而设计的(AFAIK Mon-like架构是为低功耗处理器设计的,而Cove-like架构是为桌面处理器设计的)更不用说英特尔最初肯定没有计划在同一芯片中混合这两种架构。
AMDZen 1/Zen 2上,movabs为0.25,lea为0.5,因此前者也更好。在AMD Zen 3/Zen 4上,两者的吞吐量倒数为0.25,因此它们在这种架构上同样快。
也就是说,前者占用更多的空间,而且可能比后者更慢,所以后者可能更好地超出热循环。事实上,指令被解码为µops一次,然后放入缓存中进行相对较短的循环,但是解码通常是执行一次的大型代码的瓶颈(没有热循环或非常大的一个和可能需要从RAM或L3中获取的代码)。

v8wbuo2f

v8wbuo2f2#

制造商更喜欢RIP相关的莱亚,因为对于大多数使用情形而言,代码大小比它只能在Intel CPU的一个端口上执行这一事实更重要(https://uops.info/和Jérôme Richard的答案)。也因为它与位置无关,不像movabs,所以我们至少应该考虑两种选择。(Linux上的PIE可执行文件中支持64位绝对地址的修复 *,但这意味着动态链接器必须重新Map可写页面,然后再Map回可执行页面。“文本重定位”通常是要避免的,而ld会发出警告。因此,对于所有主流操作系统上的现代可执行文件中的非JIT用例,这对movabs来说是一个很大的负面影响。)
根据Agner Fog的测试,Sandybridge系列上的movabs具有实际上具有超过32个有效位的立即数,需要额外的时间从uop缓存中获取,并且由于需要借用空间,因此在打包到uop缓存中时存在一些限制。(https:agner.org/optimize/microarchitecture.pdf#page=125-在Sandybridge部分中:这在后来的CPU中可能已经改变了一些)。10字节x86机器码大小成本通常仍然是要避免的,即使没有任何uop缓存损失。
如果需要在每个时钟周期内多次将地址输入寄存器,请从循环外部设置的另一个寄存器复制,或从内存加载(使用RIP相对寻址模式或从堆栈空间加载)。
在一个循环中进行函数调用,这将是一个比1/clock更大的瓶颈。
当然,如果您的地址实际上位于虚拟地址空间的低32位,请使用5字节的mov eax, 0x1234567(就像用于静态数据的Linux非PIE可执行文件,或者像x32 ABI。对于JIT,如果您想启用它,可以使用Linux的MAP_32BIT。)
相关:How to load address of function or label into register涵盖了选择的细节,但没有讨论莱亚吞吐量权衡。

相关问题