首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >链路时间优化和内联

链路时间优化和内联
EN

Stack Overflow用户
提问于 2011-08-12 21:33:10
回答 6查看 11.7K关注 0票数 19

在我的经验中,有许多代码显式地使用内联函数,这是一种权衡:

  1. 代码变得不那么简洁,更不容易维护。
  2. 有时,内联可以大大提高运行时性能。
  3. 衬里是在一个固定的时间点决定的,可能对它的用途没有非常好的预见性,也没有考虑到所有(未来)周围的情况。

问题是:链接时间优化(例如在GCC中)是否使手动内联(例如,在C99中声明一个“内联”函数并提供一个实现)过时了?我们自己不需要考虑大多数功能的内联,这是真的吗?那些总是从内联中受益的函数(例如,deg_to_rad(x) )怎么办?

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

更新:我经常看到反对“内联”的人,有人认为这是过时的。然而,就我个人而言,我确实经常看到显式内联函数:作为在类体中定义的函数。

EN

回答 6

Stack Overflow用户

发布于 2011-08-13 03:22:42

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

配置文件引导优化应该是解决这一问题的一个步骤,但是如果您曾经尝试过它,您可能已经注意到,您可以在0-2%的范围内获得性能上的波动,而且它可以是两个方向中的任何一个!:-)它仍然是一个正在进行的工作。

如果性能是您的最终目标,并且您确实知道您正在做什么,并且真的对您的代码进行了彻底的分析,那么真正需要的是一种方法,让编译器在每次调用的基础上内联或不内联,而不是一个每个函数的提示。在实践中,我通过使用编译器特定的"force_no_inline“类型提示(我不希望内联)和函数单独的"force_inline”副本(在很少情况下失败的情况下)来实现这一点,以便在需要内联时使用它。如果有人知道如何使用编译器特定的提示(对于任何C/C++编译器)以更干净的方式完成此操作,请让我知道。

为了具体地解决你们的问题:

1.代码变得不那么简洁,更不容易维护。

一般来说,不-它只是一个关键字提示,控制它是如何内联的。然而,如果你像我在最后一段中所描述的那样跳过圈,那么是的。

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

当把编译器留给自己的设备时--是的,当然可以,但大多数情况下并非如此。编译器有很好的启发式方法,虽然并不总是最优的内联决策。就关键字而言,编译器可能完全忽略关键字,或者使用to关键字作为弱提示--一般来说,它们似乎与内联代码相反,因为内联代码会标记它们的启发式(比如将16k函数内联到一个未滚动的16x循环中)。

3.衬里是在一个固定的时间点决定的,可能对它的用途没有非常好的预见性,也没有考虑到所有(未来)周围的情况。

是的,它使用静态分析。动态分析可以从您的洞察力和手动控制内联的基础上,或理论上从PGO (这仍然很糟糕)。

票数 14
EN

Stack Overflow用户

发布于 2020-04-22 14:10:20

GCC 9 Binutils 2.33实验表明LTO可以嵌入

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

main.c

代码语言:javascript
复制
int notmain(void);

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

notmain.c

代码语言:javascript
复制
int notmain(void) {
    return 42;
}

用LTO编译并反汇编:

代码语言:javascript
复制
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

拆卸输出:

代码语言:javascript
复制
   0x0000000000001040 <+0>:     b8 2a 00 00 00  mov    $0x2a,%eax
   0x0000000000001045 <+5>:     c3      retq 

因此,我们看到没有callq或其他跳转,这意味着调用是在两个对象文件之间内联的。

但是,如果没有-flto,我们可以看到:

代码语言:javascript
复制
   0x0000000000001040 <+0>:     f3 0f 1e fa     endbr64 
   0x0000000000001044 <+4>:     e9 f7 00 00 00  jmpq   0x1140 <notmain>

因此,如何有一个JMPQ,这意味着调用没有内联。

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

因此,是的,如果您使用的是-flto,您不需要担心将定义放入标头中,这样它们就可以内联。

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

在Ubuntu19.10 amd64上测试。

票数 6
EN

Stack Overflow用户

发布于 2011-08-13 00:23:22

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

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

想一想:是什么使一个函数成为一个很好的内联选择?除了大小因子,优化器需要知道这个函数被调用的频率,从哪里调用,程序中有多少其他函数是可行的内联候选函数,以及--信不信由你--函数是否被调用过。优化(即内联)一个甚至一次都不被调用的函数是浪费时间和资源。但是,优化器如何知道函数从未被调用?嗯,它不能。除非它已经扫描了整个程序。这是链接时间优化变得至关重要的地方。

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/7046547

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档