performance - 地址操作数如何影响机器代码的性能和大小?

标签 performance optimization assembly x86 x86-64

从 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/

相关文章:

ios - 多线程时 UITableView 无响应

c - 基于函数的 CPE,如何计算下限?

c++ - 如何帮助编译器根据输入的约束优化我的程序?

c++ - 如何编写快速(低级)代码?

assembly - 如何将AVX ymm寄存器中的所有值设置为相同(均为0/1/特定值)?

c++ - 为什么使用 leal 而不是 incq?

assembly - ESP 在/proc/pid/maps 和二进制文件中不同

mysql - 列上的索引 - 两个单独的索引 VS 一组索引?

performance - 当您的算法依赖于惰性时,如何修复由惰性引起的空间泄漏

Android Studio Gradle 同步/构建性能