链接时优化和内联

时间:2011-08-12 21:33:11

标签: c++ c optimization gcc

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

  1. 代码变得不那么简洁,而且可维护性稍差。
  2. 有时,内联可以大大提高运行时性能。
  3. 内联是在一个固定的时间点决定的,可能没有对其用途的非常好的预知,或者没有考虑所有(未来)周围环境。
  4. 问题是:链接时优化(例如,在GCC中)是否呈现手动内联,例如,在C99中声明一个“内联”函数并提供一个实现,已过时?我们是否真的不需要考虑自己内联大多数函数?那些总是从内联中受益的函数怎么样,例如,deg_to_rad(x)?

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

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

6 个答案:

答案 0 :(得分:11)

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

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

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

专门针对您的观点:

  
    

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

  

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

  
    

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

  

当将编译器留给自己的设备时 - 是的,它当然可以,但绝大多数都没有。编译器具有良好的启发式,虽然不总是最佳的内联决策,但仍然很好。特别是对于关键字,编译器可能完全忽略关键字,或者使用关键字作为弱提示 - 通常它们似乎不会对内联代码产生不利影响,这些代码会标记其启发式(例如将16k函数内联到展开的16x循环中)。 p>

  
    

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

  

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

答案 1 :(得分:3)

  

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

This article似乎回答“是的”

  

想一下:什么把一个功能变成一个好的候选人   内联?除了大小因素,优化器需要知道如何   通常会调用此函数,从中调用多少其他函数   该计划中的职能是内联的可行候选人 -   信不信由你 - 这项功能是否被召唤过。优化   (即内联)一个甚至一次不被调用的函数是浪费   时间和资源。但是优化器怎么能知道函数是什么呢   从未打电话?好吧,它不能。除非它扫描了整个   程序。这就是[链接时优化]变得至关重要的地方。

答案 2 :(得分:1)

如果链接时间优化与编译时优化一样快,那么它将消除对编译器提示的需要。不幸的是,它通常不会比编译时优化快,所以它是整体构建速度与该构建的整体优化质量之间的权衡。

此外,在标题中定义函数时仍需要使用内联。否则,如果在多个转换单元中使用这些函数的多个定义,则会出现链接器错误。

答案 3 :(得分:1)

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

对于那些好奇ld是否在目标文件中内联的人,下面是一个快速实验,确认它可以:

main.c

int notmain(void);

int main(void) {
    return notmain();
}

notmain.c

int notmain(void) {
    return 42;
}

使用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

输出:

   0x0000000000001040 <+0>:     b8 2a 00 00 00  mov    $0x2a,%eax
   0x0000000000001045 <+5>:     c3      retq 

是的,没有callq,内联。

没有-flto

   0x0000000000001040 <+0>:     f3 0f 1e fa     endbr64 
   0x0000000000001044 <+4>:     e9 f7 00 00 00  jmpq   0x1140 <notmain>

是的,如果您使用的是-flto,则不必担心将定义放在标头中以便可以内联它们。

标头中包含定义的主要缺点是它们可能会减慢编译速度。对于C ++模板,您可能还对显式模板实例化感兴趣:Explicit template instantiation - when is it used?

在Ubuntu 19.10中测试。

答案 4 :(得分:0)

  1. 我认为内联关键字不会影响可维护性,只是简单的简洁。 (意见)
  2. 有时内联会降低运行时性能:http://www.parashift.com/c++-faq-lite/inline-functions.html#faq-9.3
  3. 编译器对于内联非常聪明,我听说Visual Studio几乎完全忽略它们并决定自己内联。
  4. does link-time optimization render manual inlining, obsolete? 完全没有,使得内联关键字过时的优化器在链接时间之前就开始了。

答案 5 :(得分:-1)

项目33 - Scott Myers - 第二版 - 有效的C ++让人想起。

你必须记住关键字static wrt inline!现在有一个黄蜂巢!