【问题标题】:complier generating a mov back and forth on eax编译器在 eax 上来回生成 mov
【发布时间】:2019-08-05 14:20:28
【问题描述】:
int test1(int a, int b) {
    if (__builtin_expect(a < b, 0))
        return a / b;
    return b;
}

被clang用-O3 -march=native编译到

test1(int, int):                             # @test1(int, int)
        cmp     edi, esi
        jl      .LBB0_1
        mov     eax, esi
        ret
.LBB0_1:
        mov     eax, edi
        cdq
        idiv    esi
        mov     esi, eax
        mov     eax, esi  # moving eax back and forth
        ret

为什么eaxidiv 之后来回移动?

gcc 有类似的行为,所以这似乎是有意的。

带有-O3 -march=native 的gcc 将代码编译为

test1(int, int):
        mov     r8d, esi
        cmp     edi, esi
        jl      .L4
        mov     eax, r8d
        ret
.L4:
        mov     eax, edi
        cdq
        idiv    esi
        mov     r8d, eax
        mov     eax, r8d  #back and forth mov
        ret

godbolt

【问题讨论】:

  • 我不认为这是故意的。似乎expect 混淆了两个编译器。
  • @geza 我发现两个编译器不太可能有相同的问题
  • 如果去掉expect,就可以看到额外的move是从哪里来的。

标签: c++ gcc assembly x86-64 micro-optimization


【解决方案1】:

这不是一个完整的谜题解决方案,但应该提供一些线索。

没有__builtin_expect,clang 生成:

test2(int, int):                             # @test2(int, int)
        mov     ecx, esi
        cmp     edi, esi
        jge     .LBB1_2
        mov     eax, edi
        cdq
        idiv    ecx
        mov     ecx, eax
.LBB1_2:
        mov     eax, ecx
        ret

虽然这里的寄存器分配仍然很奇怪,但至少是有道理的:如果采用分支,ecx 中的b 的值将作为返回值转移到eax。如果不取,则除法的结果(在eax 中)必须转移到ecx,以便与其他情况在同一个寄存器中。

__builtin_expect 可能会说服编译器在编译过程后期采用分支的特殊情况,孤立.LBB1_2 标签并导致它最终不在程序集中。

【讨论】:

  • 有趣,也许在某一时刻,这些指令之间存在基本块边界。 Clang 的输出将 cmets 置于不是分支目标的基本块边界上,例如 # %bb.2:,但如果这个假设是正确的,那么必须在这个错过的优化确定下来之后才能确定。我们确实没有在 mov 指令之间看到 % bb 注释,所以至少在最终的 asm 输出时间 clang 知道它不是 BB 边界。
  • @PeterCordes 如果你关闭标签过滤,孤儿标签实际上是存在的。
  • return b; 之前添加一个volatile int i=0; 可以避免gcc 和clang 错过的优化(以存储到红色区域为代价,所以你实际上并不想要这个)。但它不会改变整体功能布局。 godbolt.org/z/Fw1UOC
【解决方案2】:

idiv esi 是 32 位操作数大小,因此 EAX 已经零扩展以填充 RAX。因此复制到 ESI 或 R8D 并返回对 EAX 中的值没有影响。 (并且调用约定无论如何都不需要零扩展或符号扩展至 64 位;32 位类型在 32 位寄存器中返回,高位 32 可能是垃圾。)

这看起来纯粹是一个错过的优化。 (也没有微架构性能的原因,这将是一件好事。)

【讨论】:

  • 有趣的是,如果分支不在代码中,编译器不会发出任何动作。请参阅我的答案,了解编译器如何获得这个想法。
猜你喜欢
  • 2012-08-08
  • 2020-04-16
  • 2012-10-10
  • 2014-09-12
  • 1970-01-01
  • 2017-05-05
  • 2016-05-29
相关资源
最近更新 更多