c – 早期if语句的函数中不需要的弹出指令

前端之家收集整理的这篇文章主要介绍了c – 早期if语句的函数中不需要的弹出指令前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
在玩godbolt.org时,我注意到 gcc(6.2,7.0快照),clang(3.9)和icc(17)在编译接近于
int a(int* a,int* b) {
  if (b - a < 2) return *a = ~*a;

  // register intensive code here e.g. sorting network
}

将(-O2 / -O3)编译成这样的东西:

push    r15
    mov     rax,rcx
    push    r14
    sub     rax,rdx
    push    r13
    push    r12
    push    rbp
    push    rbx
    sub     rsp,184
    mov     QWORD PTR [rsp],rdx
    cmp     rax,7
    jg      .L95
    not     DWORD PTR [rdx]
 .L162:
    add     rsp,184
    pop     rbx
    pop     rbp
    pop     r12
    pop     r13
    pop     r14
    pop     r15
    ret

这显然在b-a<的情况下具有巨大的开销. 2.如果-Os gcc编译为:

mov     rax,rcx
    sub     rax,7
    jg      .L74
    not     DWORD PTR [rdx]
    ret
.L74:

这让我觉得没有代码阻止编译器发出这个更短的代码.

编译器有没有理由这样做?有没有办法让他们编译到较短的版本而不编译大小?

这是an example on Godbolt再现这个.它似乎与递归的复杂部分有关

解决方法

这是一个已知的编译器限制,请参阅我对该问题的评论. IDK为什么存在;也许很难让编译器在没有完成保存注册的情况下决定他们可以做什么而不会溢出.

将早期检查拉入包装器通常很有用,因为它足够小,可以内联.

看起来现代gcc实际上有时可以回避这个编译器限制.

在Godbolt编译器资源管理器上使用你的例子,添加第二个调用者就足以得到gcc6.1 -O2来为你分割函数,所以它可以内联到早期的第二个调用者并进入外部可见的square() (如果没有采用提前返回路径,则以jmp square(int *,int *)[clone .part.3]结束).

code on Godbolt,注意我添加了-std = gnu 14,这是clang编译你的代码所必需的.

void square_inlinewrapper(int* a,int* b) {
  //if (b - a < 16) return;  // gcc inlines this part for us,and calls a private clone of the function!

  return square(a,b);
}

# gcc6.1 -O2  (default / generic -march= and -mtune=)
    mov     rax,rsi
    sub     rax,rdi
    cmp     rax,63
    jg      .L9
    rep ret
.L9:
    jmp     square(int*,int*) [clone .part.3]

square()本身编译为同一个东西,调用具有大量代码的私有克隆.来自克隆内部的递归调用调用包装器函数,因此在不需要时它们不会执行额外的推/弹工作.

即使在-O3,即使没有其他呼叫者,gcc7也不会这样做.它仍然将其中一个递归调用转换为循环,但另一个只是再次调用函数.

Clang 3.9和icc17也没有克隆函数,所以你应该手动编写可内联的包装器(如果需要检查,则更改函数的主体以用于递归调用).

您可能想要命名包装器方块,并将主体重命名为专用名称(如static void square_impl).

猜你在找的C&C++相关文章