x86 寄存器重命名的成本

标签 c assembly x86-64 renaming register-allocation

以下代码在 amd64 上用 gcc 或 clang 编译

// gcc -O2 file.c -c
int f(int a, int b, int c, int d)
{
    return a & b & c & d;
}

生成以下程序集:

0000000000000000 <f>:
  0:    89 d0                   mov    %edx,%eax
  2:    21 c8                   and    %ecx,%eax
  4:    21 f0                   and    %esi,%eax
  6:    21 f8                   and    %edi,%eax
  8:    c3                      retq  

由于按位 and 应该是关联的,因此人们会假设将成对累加到两个寄存器然后 and 这两个寄存器会更有效。这将打破依赖关系并允许在具有多个 ALU 的 CPU 上并行执行。

由于编译器将 放入同一个寄存器以进行所有操作,我假设它依赖于 cpu 能够进行寄存器重命名以打破依赖关系本身。

CPU 的寄存器重命名功能是否没有成本并且在 amd64 上始终可用,或者为什么编译器会这样编译代码?

更新:

我发现如果给 gcc 传递一个更高的 tree-assoc-width 值,它可以执行预期的依赖链中断:

--param tree-reassoc-width=2

最佳答案

这看起来像是编译器不够聪明。尽管 Intel 的 Ivy Bridge 和 Haswell 微架构支持移动消除,所以 mov %edx,%eax; and %ecx, %eax 实际上变成了 and %ecx, %edx -->%eax,这个序列仍然需要三个周期(忽略这样一个小的顺序依赖链的事实会被适度的无序执行窗口隐藏)。如果编译器聪明的话,可能会生成更像下面这样的东西:

and    %esi,%edi
and    %edx,%ecx
mov    %edi,%eax
and    %ecx,%eax
retq  

如您所述,这将打破依赖链。 (通过移动消除,最后三个指令没有数据依赖性,因此如果函数调用是一条指令[并且 L2 和 L3 未命中]并且在前端等待处理指令缓存未命中时提交了先前的指令 并且在提交返回指令后读取了一个零开销计时器[假设返回时没有目标错误预测]可能比 gcc 生成的代码花费一个周期。)两个-宽序处理器将执行 和 %esi,%edi; and %edx,%ecx 在一个循环中,在下一个循环中移动 %edi,%eax,并且 and %ecx,%eax; retq 在第三个,而对于 gcc 生成的代码 mov %edx,%eax 将在第一个周期执行,和 %ecx,%eax第二个是和%esi,%eax 第三个是和%edi,%eax; retq 在第四个。

寄存器重命名不会破坏真正的数据依赖链,但会移除name 依赖(Write-After-Read [写入应该发生在读取之后,因此读取得到旧值] 和Write-After-Write 危害是名称依赖性 [从技术上讲,可以删除没有读取的写入,但是检测到没有进行读取并且以后的写入不是推测性的通常被认为是不值得的];阅读-After-Write 是真正的数据依赖,Read-After-Read 没有依赖)。在乱序执行的实现中,寄存器重命名是普通操作的一部分;从这个意义上说,它可以被认为是“无成本”的。

关于x86 寄存器重命名的成本,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22675733/

相关文章:

c - MPI 共享内存和四精度的段错误

c - 如何解决 Visual Studio 2005 中的错误 C2664 _vswprintf_c_l 错误?

c - 图形驱动程序 "hello world"示例?

c - gcc 参数寄存器在 x86-64 上溢出

c++ - 将路径传递给 CreateProcessA

c++ - 在 C/C++ 中将输出 shell 流(cout 或 printf)显示到 Textview 小部件(GTK+ 3)?

assembly - 我教科书中的算术右移示例,当 MSB 为零时,移位为 1

c - MIPS 中的错误寻址 - 矩阵乘法

c++ - 依赖机器架构意味着什么?

c++ - strlen vs停止为零的字符串操作的性能