jta*_*lor 7 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;
}
Run Code Online (Sandbox Code Playgroud)
产生以下装配:
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
Run Code Online (Sandbox Code Playgroud)
由于按位and应该是关联的,人们会认为将成对方式累加到两个寄存器然后再and这两个寄存器会更有效.这将破坏依赖性并允许在具有多个ALU的cpu上并行执行.
由于编译器and为所有操作进入相同的寄存器,我假设它依赖于cpu能够进行寄存器重命名以打破依赖本身.
cpu的寄存器重命名功能是否没有成本,并且始终在amd64上可用,或者为什么编译器会像这样编译代码?
更新: 我发现如果为树关联宽度传递更高的值,gcc可以执行预期的依赖关系链断开:
--param tree-reassoc-width=2
Run Code Online (Sandbox Code Playgroud)
这看起来像编译器只是不够聪明.虽然英特尔的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
Run Code Online (Sandbox Code Playgroud)
如你所知,这将打破依赖链.(带的移动消除,最后3个指令没有数据的依赖关系,因此,如果函数调用的指令[和L2和L3缺失],并承诺而前端等待指令高速缓存未命中的先前指令将被处理和一个提交返回指令后读取零开销计时器[假设返回时没有目标错误预测]可能比gcc生成的代码少一个周期.)一个两宽的有序处理器将and %esi,%edi; and %edx,%ecx在一个周期内执行,move %edi,%eax在下一个周期内执行和and %ecx,%eax; retq在第三,而对于GCC生成的代码mov %edx,%eax将被在第一周期内执行,and %ecx,%eax在第二,and %esi,%eax第三,和and %edi,%eax; retq第四.
寄存器重命名不会破坏真正的数据依赖关系链,但会删除名称依赖关系(Write-After-Read [在读取后应该发生写入,因此read获取旧值]和Write-After-Write危险是名称依赖关系[从技术上讲,可以删除没有读取的写入,但是检测到没有读取并且后面的写入不是推测性的通常不被认为是值得的;; Read-After-Write是真正的数据依赖性而Read-After-Read没有依赖).在无序执行的实现中,寄存器重命名是普通操作的一部分; 从这个意义上说,可以认为它"没有成本".
| 归档时间: |
|
| 查看次数: |
881 次 |
| 最近记录: |