链接时优化和内联

cco*_*com 15 c c++ optimization gcc

根据我的经验,有许多代码明确使用内联函数,这需要权衡:

  1. 代码变得不那么简洁,而且可维护性稍差.
  2. 有时,内联可以大大提高运行时性能.
  3. 内联是在一个固定的时间点决定的,可能没有对其用途的非常好的预知,或者没有考虑所有(未来)周围环境.

问题是:链接时优化(例如,在GCC中)是否呈现手动内联,例如,在C99中声明一个"内联"函数并提供一个实现,已经过时了?我们是否真的不需要考虑自己内联大多数函数?那些总是从内联中受益的函数呢,例如deg_to_rad(x)?

澄清:我不是在考虑同一个翻译单元中的函数,而是考虑逻辑上应该存在于不同翻译单元中的函数.

更新:我经常看到反对"内联",并建议过时.但是,就个人而言,我确实经常看到明确的内联函数:作为类体中定义的函数.

Cro*_*ey9 11

即使使用LTO,编译器仍然必须使用启发式方法来确定是否为每个调用内联函数(注意它不是按函数决定,而是每次调用).启发式考虑了因素 - 例如它是循环,是否展开循环,函数有多大,全局调用的频率等等.编译器肯定永远无法准确确定代码的调用频率,以及代码扩展是否可能在编译时烧掉特定CPU的指令/跟踪/循环/微代码高速缓存.

配置文件引导优化应该是解决这个问题的一个步骤,但是如果你曾经尝试过,你很可能已经注意到你可以获得0-2%的性能提升,它可以在无论方向!:-)这仍然是一项正在进行中的工作.

如果性能是您的最终目标,并且您真的知道自己在做什么,并且真正对代码进行全面分析,那么真正需要的是一种告诉编译器内联或不内联的方法,而不是每个功能提示.在实践中,我通过使用编译器特定的"force_no_inline"类型提示来处理这个,我不想内联的情况,以及一个单独的"force_inline"副本(或在极少数情况下这个失败的宏)我希望它内联的函数.如果有人知道如何以更清晰的方式使用编译器特定的提示(对于任何C/C++编译器),请告诉我.

要专门解决您的观点:

1.代码变得不那么简洁,而且可维护性稍差.

通常,不 - 它只是一个控制内联方式的关键字提示.但是如果你像我在上一段中描述的那样跳过篮球,那么是的.

2.有时,内联可以大大提高运行时性能.

将编译器留给自己的设备时 - 是的,它肯定可以,但大多数情况下都没有.编译器具有良好的启发式,虽然不总是最佳的内联决策,但仍然很好.特别是对于关键字,编译器可能完全忽略关键字,或者使用关键字作为弱提示 - 一般来说,它们似乎对内联代码有害,因为红色标记了它们的启发式(比如将16k函数内联到一个循环中展开16x).

3.内联是在一个固定的时间点决定的,可能没有对其用途的非常好的预知,或者没有考虑所有(未来)周围环境.

是的,它使用静态分析.动态分析可以来自您的洞察力,可以手动控制每个呼叫的内联,或理论上来自PGO(仍然很糟糕).


Cir*_*四事件 9

GCC 9 Binutils 2.33实验证明LTO可以内联

对于那些好奇是否ld跨对象文件内联的人,这里有一个快速实验,证实它可以:

主程序

int notmain(void);

int main(void) {
    return notmain();
}
Run Code Online (Sandbox Code Playgroud)

notmain.c

int notmain(void) {
    return 42;
}
Run Code Online (Sandbox Code Playgroud)

使用LTO编译并反汇编:

gcc -O3 -flto -ggdb3 -std=c99 -Wall -Wextra -pedantic -c -o main.o main.c
gcc -O3 -flto -ggdb3 -std=c99 -Wall -Wextra -pedantic -c -o notmain.o notmain.c
gcc -O3 -flto -ggdb3 -std=c99 -Wall -Wextra -pedantic -o main.out notmain.o main.o
gdb -batch -ex "disassemble/rs main" main.out
Run Code Online (Sandbox Code Playgroud)

反汇编输出:

   0x0000000000001040 <+0>:     b8 2a 00 00 00  mov    $0x2a,%eax
   0x0000000000001045 <+5>:     c3      retq 
Run Code Online (Sandbox Code Playgroud)

所以我们看到没有跳转callq或其他跳转,这意味着调用是跨两个对象文件内联的。

然而,如果没有-flto,我们会看到:

   0x0000000000001040 <+0>:     f3 0f 1e fa     endbr64 
   0x0000000000001044 <+4>:     e9 f7 00 00 00  jmpq   0x1140 <notmain>
Run Code Online (Sandbox Code Playgroud)

那么怎么有一个JMPQ,这意味着调用没有内联。

请注意,编译器选择了 JMPQ,它不会像更简单的 CALLQ 那样进行任何堆栈更改作为优化,我认为这是尾调用优化的一个微不足道的最小情况。

所以是的,如果您正在使用-flto,则无需担心将定义放入标头中以便将它们内联。

在标头中定义的主要缺点是它们可能会减慢编译速度。对于 C++ 模板,您可能还对显式模板实例化感兴趣:显式模板实例化 - 何时使用?

在 Ubuntu 19.10 amd64 上测试。


Gna*_*wme 5

问题是:链接时优化(例如,在 GCC 中)是否会导致手动内联,例如,在 C99 中声明“内联”函数并提供实现,是否已过时?

这篇文章似乎回答“是的:”

想一想:什么使函数成为内联的良好候选者?除了大小因素之外,优化器还需要知道该函数被调用的频率、从何处调用、程序中还有多少其他函数是内联的可行候选者以及(不管你相信与否)该函数是否曾经被调用过。叫。优化(即内联)一个甚至一次都没有被调用的函数是浪费时间和资源。但是优化器如何知道函数从未被调用过呢?嗯,它不能。除非它扫描了整个程序。这就是[链接时优化]变得至关重要的地方。


Ed *_*eal -2

第 33 项 - Scott Myers - 第二版 - 高效的 C++ 浮现在脑海中。

您必须记住关键字 static wrt inline!现在有马蜂窝了!

  • 您愿意为我们这些没有那本书的人分享该项目的内容吗? (8认同)