从32位CPU模式开始,有x86架构可用的扩展地址操作数。可以指定基址,位移,索引寄存器和缩放因子。
例如,我们希望跨越一个32位整数列表(每32位字节长数据结构中的前两位,%rdi
作为数据索引,%rbx
为基指针)。
addl $8, %rdi # skip eight values: advance index by 8
movl (%rbx, %rdi, 4), %eax # load data: pointer + scaled index
movl 4(%rbx, %rdi, 4), %edx # load data: pointer + scaled index + displacement
据我所知,这种复杂的寻址适用于单个机器代码指令。但是这种操作的成本是多少,与独立指针计算的简单寻址相比如何呢?
addl $32, %rbx # skip eight values: move pointer forward by 32 bytes
movl (%rbx), %eax # load data: pointer
addl $4, %rbx # point next value: move pointer forward by 4 bytes
movl (%rbx), %edx # load data: pointer
在后一个例子中,我引入了一个额外的指令和一个依赖。但整数加法非常快,我获得了更简单的地址操作数,并且不再有乘法。另一方面,由于允许的缩放因子是2的幂,所以乘法降低到位移,这也是非常快的操作。仍然可以用一次添加替换两次添加和一次移位。
这两种方法之间的性能和代码大小差异是什么?是否有使用扩展寻址操作数的最佳实践?
或者,从C程序员的角度提问,更快的是:数组索引还是指针算法?
是否有任何用于大小/性能调整的汇编编辑器?我希望我能看到每个汇编指令的机器代码大小,它在时钟周期中的执行时间或依赖图。有成千上万的装配怪将从这样的应用程序中受益,所以我敢打赌这样的事情已经存在!
答案 0 :(得分:2)
地址算术非常快,应尽可能使用。
但这个问题错过了。
首先,你不能使用地址算术乘以32 - 8是最大可能的常数。
代码的第一个版本未完成,因为它需要第二条指令,即递增rbx
。因此,我们有以下两种变体:
inc rbx
mov eax, [8*rbx+rdi]
VS
add rbx, 8
mov eax, [rbx]
这样,两种变体的速度将是相同的。大小相同 - 也是6个字节。
那么,什么代码更好只取决于程序上下文 - 如果我们有一个已经包含所需数组单元格地址的寄存器 - 使用mov eax,[rbx]
如果我们有包含单元格索引的寄存器和包含起始地址的另一个寄存器,则使用第一个变体。这样,在算法结束后,我们仍将在rdi中拥有数组的起始地址。
答案 1 :(得分:2)
您的问题的答案取决于给定的本地程序流程情况 - 反过来,它们可能在处理器制造商和架构之间有所不同。微观分析一两条指令通常是没有意义的。您有一个多阶段管道,多个整数单元,缓存以及更多可以发挥作用的因素,您需要将其纳入分析。
您可以通过查看生成的汇编代码并分析为什么序列看起来与将要处理它的不同硬件单元相关的方式来尝试逆向工程。
另一种方法是使用分析器并尝试使用不同的构造来查看哪些有效,哪些无效。
您还可以下载gcc的源代码,看看真正酷的程序员如何评估序列以生成最快的代码。有一天你可能成为其中之一: - )
在任何情况下,我都希望您能得出结论,最佳序列根据处理器,编译器,优化级别和周围指令而有很大差异。如果您使用C,源代码的质量非常重要:垃圾输入=垃圾输出。