从 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 程序员的角度问,哪个更快:数组索引或指针算术?
是否有用于尺寸/性能调整的程序集编辑器?我希望能够看到每个汇编指令的机器代码大小、其以时钟周期为单位的执行时间或依赖图。有数以千计的组装狂可以从这样的应用程序中受益,所以我敢打赌这样的东西已经存在了!
最佳答案
地址算术非常快,如果可能的话应始终使用。
但是这个问题遗漏了一些东西。
首先,您无法使用地址算术乘以 32 - 8 是最大可能的常数。
代码的第一个版本并不完整,因为它需要第二条指令,即递增rbx
。因此,我们有以下两种变体:
inc rbx
mov eax, [8*rbx+rdi]
对比
add rbx, 8
mov eax, [rbx]
这样,两个变体的速度将是相同的。大小相同 - 也是 6 字节。
因此,哪种代码更好仅取决于程序上下文 - 如果我们有一个已经包含所需数组单元地址的寄存器 - 使用 mov eax, [rbx]
如果我们有包含单元索引的寄存器和另一个包含起始地址的寄存器,则使用第一个变量。这样,算法结束后,我们仍然可以得到rdi中数组的起始地址。
关于performance - 地址操作数如何影响机器代码的性能和大小?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18552677/