编译器和链接器内联函数的区别是什么?

15

我想知道在链接器层面或编译器层面内联函数是否有执行速度上的区别?例如,如果我将所有函数放在.cpp文件中并依赖于链接器进行内联,那么这种内联是否可能比在头文件中定义某些函数以供编译器选择内联或通过编译器进行所有内联而不进行任何链接和统一构建更有效率?

如果链接器同样有效,那么为什么还要在编译器层面显式地内联函数?这只是为了方便吗?比如说,只有一个单行构造函数,因此不需要.cpp文件吗?

我想这可能取决于编译器,如果是这样,我最感兴趣的是Visual C++(Windows)和gcc(Linux)。

谢谢


2
你使用哪个链接器可以进行内联处理? - user2100815
4
Visual C++有所谓的链接时代码生成功能,它似乎能够在发出代码时做任何事情。 - sharptooth
1
连接器正在收集所有模块,然后再次调用编译器完成代码生成。这允许在.cpp文件之间进行内联等操作。 - Bo Persson
链接器不会内联代码,它们不知道如何删除编译器生成的机器码。/LTCG有着非常不同的目的,它添加了代码以提供仪表数据来优化可执行映像布局。那些代码是临时的。 - Hans Passant
@Hans Passant: http://msdn.microsoft.com/en-us/library/xbf3tbeh(v=vs.80).aspx中提到,除其他功能外,还包括:*跨模块内联*。无论如何,还有其他链接器可以实现它,因此声称*链接器不会内联代码*是夸大其词的(如果您想考虑在先前的情况下,链接器并没有真正*优化*而是调用编译器进行优化)。 - David Rodríguez - dribeas
4个回答

3
一般来说,在其他条件相同的情况下,越靠近执行(编译->链接->(可能是JIT)->执行)优化就越容易获得更多的数据,并且可以进行更好的优化。因此,除非优化器很笨,否则当链接器进行内联时,您应该期望获得更好的结果-链接器将了解更多有关调用上下文的信息并进行更好的优化。请参见这里了解更多信息。

好的回答...但是据我理解,问题恰恰在于是否其他所有事情都相等。 - Konrad Rudolph
@Konrad Rudolph:嗯,没错。而且工具链可能会有漏洞,所以必须在自己的工具链上进行测试才能确保。 - sharptooth
1
我绝对不同意这个观点。静态编译器和链接器比JIT或动态优化拥有更多的时间来完成工作,这使得它们的工作范围更大。 - Puppy
1
@dribeas - David Rodríguez:从技术上讲,如果用于生成代码的中间数据存储足够检测到这一点,那么它确实可以。 - sharptooth
1
@sharptooth:我想表达的是,在不同的时间点上,有不同类型的优化是有意义的。为了能够优化掉它,链接器必须包含一个完整的代码优化器(不仅仅是内联程序,即不仅仅是移动代码的能力,而是实际替换现有代码)。相反,JIT具有关于实际使用模式的信息,可以使其以在编译时无法实现的方式进行优化(或者需要精确的分析器输出)。 - David Rodríguez - dribeas
显示剩余4条评论

3
一般来说,在链接器运行时,您的源代码已经被编译成机器码。链接器的工作是将所有的代码片段连接在一起(可能会在此过程中修正地址)。在这种情况下,没有进行内联的余地。
但并非所有的都失去了。使用 -flto 选项编译和链接时,Gcc 提供了一种链接时优化的机制。这会导致 gcc 生成一个字节码,然后由链接器将其编译和链接为单个可执行文件。由于字节码包含比优化后的机器码更多的信息,因此链接器现在可以对整个代码库进行根本性的优化。这是编译器无法完成的任务。
有关 gcc 的更多详细信息,请参见 此处。不太确定 VC++ 是否也如此。

VC++ 相关链接为 /LTCG/GL - ildjarn

2

内联通常在单个翻译单元(.cpp文件)中执行。当您调用另一个文件中的函数时,它们永远不会被内联。

链接时间优化(LTO)改变了这一点,允许跨翻译单元进行内联。就生成的代码效率而言,它应该始终等于或优于常规链接(有时非常显着)。

之所以两个选项仍然可用,是因为LTO可能需要大量RAM和CPU - 我曾经花费几分钟时间在VC++上连接一个大型C ++项目。有时直到您发货才启用它可能并不值得。如果项目足够大,您还可能耗尽地址空间,因为它必须将所有字节码加载到RAM中。

对于编写高效代码,没有任何改变-所有相同的规则都适用于LTO。在头文件中明确定义内联函数与依赖LTO来进行内联相比,可能更有效。inline关键字只提供提示,因此没有保证,但它可能会推动它被内联,而通常情况下(无论是否使用LTO)不会被内联。


(复习)对于第一次回答来说,这是一个措辞恰当的答案。首次使用LTO时,请介绍其全名,特别是因为问题没有定义LTO,如果你的答案被接受,人们可能会在问题后立即阅读它。 - Hassan Syed
谢谢你的提示!下次回答时我会记住的。LTO当然是链接时间优化。 - Cory Nelson
抱歉,我有点困惑。一方面,您说LTO至少与显式内联一样有效,另一方面,您说显式内联可能会推动它被内联(因此我认为可能更有效),而LTO则不会将其内联? - Cookie
LTO 至少和“常规链接”(非 LTO)一样有效。无论是否使用 LTO,显式内联都有可能比让编译器选择何时进行内联更有效。 - Cory Nelson
那么这是否意味着在优化和最终执行速度方面,我们应该尽量使用内联而不是少用呢?例如,对于小类(例如代码行数少于50行),应该将所有内容都写在头文件的类定义中吗?如果有很多这些头文件被包含在多个cpp文件中,链接器是否会再次减少每个编译单元的额外大小? - Cookie
通常我会将代码行数小于等于5行的函数内联,但实际上并没有绝对的规则——需要进行性能分析才能确定。一般来说,编译器会为你做出好的决策,但如果它支持基于性能分析的优化(PGO),那么它可以更好地决定哪些函数需要内联。VC++/GCC都支持PGO。我不确定编译器是否能够合并来自多个TUs的相同函数。 - Cory Nelson

0
如果函数被内联,就没有区别。
我认为在头文件中定义内联函数的主要原因是历史。另一个原因是可移植性。直到最近,大多数编译器都不支持链接时代码生成,因此在头文件中拥有函数是必需的。当然,这会影响到几年前开始的代码库。
此外,如果您仍然针对某些不支持链接时代码生成的编译器,则没有选择。
顺便说一下,我曾经被迫添加一个pragma来请求一个特定的编译器不要内联一个在一个.cpp文件中定义的init()函数,但可能从许多地方调用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接