我问这个基本问题是为了把记录弄清楚。已经提到了this question和its currently accepted answer,这是没有说服力的。然而,second most voted answer提供了更好的洞察力,但也不是完美的。
在阅读下面的内容时,请尝试区分inline
* 关键字 * 和“内联”* 概念 *。
以下是我的观点:
inlining概念
这样做是为了保存函数的调用开销。它更类似于宏样式的代码替换。没什么好争的
inline
关键字
感知A
inline
关键字是对编译器的一个 * 请求 *,通常用于较小的函数,以便编译器可以优化它并进行更快的调用。但是,管理者可以自由地忽略它。
我对此有部分争议,原因如下:
1.更大的和/或递归函数无论如何都不会内联,编译器完全忽略inline
关键字
1.无论是否提到inline
关键字,优化器都会自动内联较小的函数。
很明显,用户对使用关键字inline
的内联函数没有任何控制权。
感知B
inline
与内联的概念无关。将inline
放在大型/递归函数之前没有帮助,而较小的函数不需要它来内联。inline
的 * 唯一 * 确定性用途是维护 * 一个定义规则 *。
也就是说,如果一个函数是用inline
声明的,那么 * 只有 * 下面的事情是强制的:
1.即使它的主体存在于多个翻译单元中(例如,包括在多个.cpp
文件中头),编译器将只生成1个定义并避免多个符号链接器错误。(注意:如果该函数的主体不同,则它是未定义的行为。
inline
函数的主体必须在所有使用它的翻译单元中可见/可访问。换句话说,在.h
中声明inline
函数并在 * 任何一个 *.cpp
文件中定义将导致其他.cpp
文件出现“未定义符号链接器错误
判决
IMO,感知“A”是完全错误的,感知“B”是完全正确的。
有一些报价标准,但我期待一个答案,从逻辑上解释,如果这个判决正确与否。
来自Bjarne Stroustrup的电子邮件回复:
“几十年来,人们一直承诺编译器/优化器在内联方面已经或即将优于人类。这在理论上可能是正确的,但对于优秀的程序员来说,这在实践中仍然是不可能的,特别是在整个程序优化不可行的环境中。明智地使用显式内联会带来很大的好处。"*
3条答案
按热度按时间qvsjd97n1#
我不确定你的说法:
较小的函数会被优化器自动“内联”,而不管是否提到内联。很明显,使用关键字
inline
,用户对函数“内联”没有任何控制权。我听说编译器可以随意忽略你的
inline
请求,但我不认为他们会完全忽略它。我查看了Github存储库中的Clang和LLVM。(谢谢开源软件!))我发现**
inline
关键字 * 确实 * 让Clang/LLVM更有可能内联函数。**搜索
在the Clang repository中搜索单词
inline
会找到标记说明符kw_inline
。看起来Clang使用了一个聪明的基于宏的系统来构建lexer和其他关键字相关的函数,所以没有像if (tokenString == "inline") return kw_inline
这样的直接函数。但是在ParseDecl.cpp中,我们看到kw_inline
导致对DeclSpec::setFunctionSpecInline()
的调用。在该函数中,我们设置一个位,如果它是一个重复的
inline
,则发出警告:在其他地方搜索
FS_inline_specified
,我们看到它是位域中的一个位,它被用于getter函数isInlineSpecified()
:搜索
isInlineSpecified()
的调用站点,我们找到了codegen,在那里我们将C++解析树转换为LLVM中间表示:Clang到LLVM
我们完成了C++解析阶段。现在我们的
inline
说明符被转换为语言中立的LLVMFunction
对象的属性。我们从Clang切换到the LLVM repository。搜索
llvm::Attribute::InlineHint
得到方法Inliner::getInlineThreshold(CallSite CS)
(带有一个看起来很可怕的无括号if
块):所以我们已经有了一个来自优化级别和其他因素的基线内联阈值,但是如果它低于全局
HintThreshold
,我们就把它提高。* (提示阈值可从命令行设置。)*getInlineThreshold()
似乎只有一个调用站点,是SimpleInliner
的成员:它在指向
InlineCostAnalysis
示例的成员指针上调用一个虚拟方法,也称为getInlineCost
。搜索
::getInlineCost()
以查找类成员的版本,我们发现一个是AlwaysInline
的成员-这是一个非标准但广泛支持的编译器特性-另一个是InlineCostAnalysis
的成员。它在这里使用它的Threshold
参数:CallAnalyzer::analyzeCall()
超过200行,并做了决定函数是否可内联的真实的实质性工作。它权衡了许多因素,但当我们阅读该方法时,我们看到它的所有计算都操纵Threshold
或Cost
。最后:但是名为
ShouldInline
的返回值实际上是用词不当。实际上,analyzeCall()
的主要目的是在CallAnalyzer
对象上设置Cost
和Threshold
成员变量。返回值仅指示当其他因素覆盖成本与阈值分析时的情况,如我们在这里看到的:否则,我们返回一个存储
Cost
和Threshold
的对象。所以在大多数情况下,我们不会返回一个是或否的决定。搜索继续!
getInlineCost()
的返回值在哪里使用?真实的决定
在
bool Inliner::shouldInline(CallSite CS)
中找到。另一个大功能。它一开始就调用getInlineCost()
。结果是
getInlineCost
分析了内联函数的 * 内在 * 成本--其参数签名、代码长度、递归、分支、链接等。- 和一些关于 * 每个 * 使用该函数的地方的聚合信息。另一方面,shouldInline()
将此信息与有关使用该函数的 * 特定 * 位置的更多数据相结合。在整个方法中,有对
InlineCost::costDelta()
的调用-它将使用analyzeCall()
计算的InlineCost
sThreshold
值。最后,我们返回一个bool
。已经决定了。在Inliner::runOnSCC()
中:InlineCallIfPossible()
根据shouldInline()
的决定进行内联。因此
Threshold
受到inline
关键字的影响,并在最后决定是否内联。因此,您的Perception B部分是错误的,因为至少有一个主要的编译器根据
inline
关键字更改了其优化行为。但是,我们也可以看到
inline
只是一个提示,其他因素可能会超过它。vshtjzan2#
两者都是正确的。
inline
的使用可能会(也可能不会)影响编译器决定内联任何特定的函数调用。所以A是正确的--它作为一个非绑定的请求,调用被内联的函数,编译器可以自由忽略。inline
的语义效果是放宽了一个定义规则的限制,允许在多个翻译单元中使用相同的定义,如B中所述。对于许多编译器来说,这是允许内联函数调用所必需的-定义必须在那一点上可用,并且编译器一次只需要处理一个翻译单元。mbjcgjjk3#
我只是想发布一个
inline
影响内联的概念验证示例。Here it is:
即使使用
-O3
,Clang 16.0.0也不会内联bar
;在这种情况下,您将在输出中看到call
指令。但是如果您将inline
添加到bar
,则这些将完全内联,并且call
指令将消失。* 尽管所有方法都是语义隐式内联的!*这个例子显然是人为设计的,但我遇到过实际情况,在这种情况下,它会导致不小的性能差异。