制造商更喜欢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涵盖了选择的细节,但没有讨论莱亚吞吐量权衡。
2条答案
按热度按时间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中获取的代码)。
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涵盖了选择的细节,但没有讨论莱亚吞吐量权衡。