编译器是否更有可能使用指定的 inline 关键字在 class 声明中内联函数?
Is the compiler more likely to inline a function in the class declaration with the inline keyword specified?
我最近在审查同事的代码时注意到他将 "inline" 关键字放在了 class 声明中定义的一堆 Getter 函数的前面。
例如
class Foo
{
public:
inline bool GetBar() const { return m_Bar; }
private:
bool m_Bar;
};
我在代码审查中建议他删除内联关键字,正如我在许多不同的地方读到的,在 class 声明中定义一个函数是由编译器解释的(MSVC 在这个大小写,但显然是 C++ 标准的一部分)作为作者希望内联函数的指示。我的感觉是,如果多余的文字没有任何用处,那只是不必要的混乱,应该删除。
他的回复如下:
inline 关键字使与此代码交互的其他程序员清楚这些函数 are/should 是内联的。
在这种情况下,许多编译器仍然会考虑 inline 关键字,并使用它来影响(阅读:增加)某种权重,该权重用于决定所述函数实际上是否是内联。
那里有 inline 关键字意味着如果出于任何原因未内联所述函数,将触发 "warn if not inlined" 警告(如果启用)。
就我个人而言,我完全不同意第一个原因。对我来说,在 class 声明中定义函数就足以表明意图。
我对最后两个原因持怀疑态度。我找不到任何信息来确认或否认有关影响某种权重的内联关键字的观点。我也无法为 class 声明中定义的函数触发 "warn if not inlined" 警告。
如果您已经读到这里,我想知道您是否对上述任何一点有任何见解?此外,如果您能指出任何相关的 articles/documentation,我将不胜感激。
谢谢!
编辑 1:添加了 LLVM(换句话说 "clang")内联代码
编辑 2:添加关于如何 "resolve" 的说明。
实际正确
第 1 点当然是不言自明的。
第 2 点是无稽之谈 - 所有现代编译器(至少是 MS、GCC 和 Clang [aka XCode])完全忽略 inline
关键字并完全基于 frequency/size 标准(根据大小 * 次数确定 "code-bloat factor",因此小函数或仅调用几次的函数更有可能被内联 - 当然 getter 将是编译器始终内联的完美选择,因为它只有两到三个指令,而且很可能比加载 this
,然后调用 getter 函数要短。
inline
关键字根本没有任何区别[并且 C++ 标准规定 class 中的定义无论如何都是 inline
]。
第 3 点是另一种可能的情况,但我认为根据其定义隐式内联这一事实应该会产生相同的结果。不久前在 Clang 邮件列表上讨论了 inline
关键字及其含义,结论是 "the compiler usually knows best".
将 inline
与虚函数一起使用通常也完全没有用,因为它们几乎总是通过 vtable 条目调用,并且不能内联。
编辑 1:
取自 LLVM 的代码 "InlineCost.cpp":
InlineCost InlineCostAnalysis::getInlineCost(CallSite CS, Function *Callee,
int Threshold) {
// Cannot inline indirect calls.
if (!Callee)
return llvm::InlineCost::getNever();
// Calls to functions with always-inline attributes should be inlined
// whenever possible.
if (CS.hasFnAttr(Attribute::AlwaysInline)) {
if (isInlineViable(*Callee))
return llvm::InlineCost::getAlways();
return llvm::InlineCost::getNever();
}
// Never inline functions with conflicting attributes (unless callee has
// always-inline attribute).
if (!functionsHaveCompatibleAttributes(CS.getCaller(), Callee,
TTIWP->getTTI(*Callee)))
return llvm::InlineCost::getNever();
// Don't inline this call if the caller has the optnone attribute.
if (CS.getCaller()->hasFnAttribute(Attribute::OptimizeNone))
return llvm::InlineCost::getNever();
// Don't inline functions which can be redefined at link-time to mean
// something else. Don't inline functions marked noinline or call sites
// marked noinline.
if (Callee->mayBeOverridden() ||
Callee->hasFnAttribute(Attribute::NoInline) || CS.isNoInline())
return llvm::InlineCost::getNever();
DEBUG(llvm::dbgs() << " Analyzing call of " << Callee->getName()
<< "...\n");
CallAnalyzer CA(TTIWP->getTTI(*Callee), ACT, *Callee, Threshold, CS);
bool ShouldInline = CA.analyzeCall(CS);
DEBUG(CA.dump());
// Check if there was a reason to force inlining or no inlining.
if (!ShouldInline && CA.getCost() < CA.getThreshold())
return InlineCost::getNever();
if (ShouldInline && CA.getCost() >= CA.getThreshold())
return InlineCost::getAlways();
return llvm::InlineCost::get(CA.getCost(), CA.getThreshold());
}
可以看出(在代码的其余部分进行了一些挖掘),只检查了 "always" 和 "never" 内联选项。 None 用于内联关键字本身。
[请注意,这是 clang 和 clang++ 的内联代码 - clang 本身在生成代码时并没有做任何特别聪明的事情,它是 "just"(让数百名为此花费数百年的程序员感到沮丧项目!)一个用于转换为 LLVM IR 的 C 和 C++ 解析器,所有好的、聪明的东西都在 LLVM 层完成——这确实是提供 "multilanguage" 编译器框架的好方法。我已经编写了一个 Pascal 编译器,尽管在编译器工作方面是一个完全的新手,但我的编译器(使用 LLVM 生成实际的机器代码)在(生成的代码的)基准测试中比 Free Pascal 更好——这都要归功于 LLVM,几乎 none 其中是我的工作——除了一些在特定基准测试中内联一些常用函数的代码]
我没有 MS 编译器的访问源(doh!),我懒得下载 gcc 只是为了检查一下。根据经验,我知道这三个函数都将内联没有 inline 关键字的函数,并且 gcc 将积极内联它可以确定只有一个调用者的函数(例如大型 static
辅助函数)
编辑 2:
解决此类问题的正确方法是制定一个编码标准,明确说明何时何地应该使用 inline
[以及 class] 中定义的函数,并且什么时候不应该这样做。如果目前,其他 classes 中的其他小 getter 函数中的 none 有 inline
,那么这个函数确实会很奇怪并且很突出。如果除某些以外的所有人都有 inline
,那可能也应该修复。
另一个轶事:我个人喜欢将 if 语句写成
if (some stuff goes here)
(space在if和括号之间,但不在里面的东西周围)
但是工作中的编码标准说:
if( some stuff goes here )
(如果和括号之间没有space,但是里面的东西space)
我不小心得到了这样的东西:
if ( some stuff goes here )
在一些待审查的代码中。我修复了该行,但决定在 if
之后用 space 修复其他 175 个 if 语句 - 该文件中总共有不到 350 个 if 语句,所以超过一半不正确...
对于您提到的特定示例,GetBar
是隐式内联的,这使得 inline
关键字变得多余。 C++ 标准的第 7.1.2 P3 节说:
A function defined within a class definition is an inline function.
另外 VC++ documentation 声明相同:
A class's member functions can be declared inline either by using the
inline keyword or by placing the function definition within the class
definition.
一般情况下,您不需要使用inline
关键字。一位 C/C++ 专家曾在 2002 年说过:
the inline keyword allows the programmer to tell the compiler
something it might have a hard time figuring out automatically.
However, in the future, compilers may be able to do a better job of
making inline decisions than programmers. When that happens, the
inline keyword might be regarded as a quaint reminder of when
programmers were forced to worry about details of code generation.
您可以找到完整的文章 here。您应该阅读整篇文章以了解为什么将关键字添加到 C99。文章还讨论了 extern inline
函数。
现在我们处于未来,现代编译器确实非常复杂,不再需要此关键字。唯一的例外是使用 extern inline
函数时。
Does the inline keyword have any affect whatsoever on the compiler's
decision to inline the function?
在 MSVC 中,inline 关键字可能会影响编译器的决定,although the compiler may choose to ignore this hint if inlining would be a net loss。
The inline keyword makes it clear to other programmers interacting
with this code that these functions are/should be inlined.
这是一个无效的理由。程序员是人。对于是否内联函数,人类通常很难做出比 MSVC 更好的决定。第二,当你告诉 him/her 一个函数应该内联时,程序员应该怎么做?编译器是进行内联的那个。警告本身不会告诉您是否必须对此做任何事情,如果是这种情况,该怎么做。
Many compilers still take the inline keyword into account in this case
and use it to affect (read: increase) some kind of weighting that is
used to decide whether or not said function would in fact be inlined.
这在 MSVC 中是正确的。但是现代的 MSVC 不再需要这个提示了。如果内联函数有好处,编译器就会知道并内联它。如果不是,它将忽略人工插入的内联关键字。
Having the inline keyword there means that "warn if not inlined"
warnings will be triggered (if enabled) if said functions are not
inlined for whatever reason.
MSVC 编译器发出警告 C4710 when a function that is selected for inlining was not inlined. This warning is disabled by default because most developers don't (and shouldn't) care about this warning. You can enable this warning, however。除非您是编译器优化研究人员并且想了解 MSVC 使用的内联算法,否则您不应启用这些警告。
我忽略了问题中特定于 C++ 的部分,根据你的 #2 项目符号只关注普通内联函数(C 或 C++):
I'm skeptical about the last two reasons. I can't find any information that either confirms or denies the the point about the inline keyword affecting some sort of weighting.
https://blog.tartanllama.xyz/inline-hints/ 提供了一些细节,日期为 2018 年 1 月。简而言之:Clang+LLVM 和 GCC 在决定是否内联函数时都会考虑关键字。这不是唯一的考虑因素,但确实会影响决策。
我最近在审查同事的代码时注意到他将 "inline" 关键字放在了 class 声明中定义的一堆 Getter 函数的前面。
例如
class Foo
{
public:
inline bool GetBar() const { return m_Bar; }
private:
bool m_Bar;
};
我在代码审查中建议他删除内联关键字,正如我在许多不同的地方读到的,在 class 声明中定义一个函数是由编译器解释的(MSVC 在这个大小写,但显然是 C++ 标准的一部分)作为作者希望内联函数的指示。我的感觉是,如果多余的文字没有任何用处,那只是不必要的混乱,应该删除。
他的回复如下:
inline 关键字使与此代码交互的其他程序员清楚这些函数 are/should 是内联的。
在这种情况下,许多编译器仍然会考虑 inline 关键字,并使用它来影响(阅读:增加)某种权重,该权重用于决定所述函数实际上是否是内联。
那里有 inline 关键字意味着如果出于任何原因未内联所述函数,将触发 "warn if not inlined" 警告(如果启用)。
就我个人而言,我完全不同意第一个原因。对我来说,在 class 声明中定义函数就足以表明意图。
我对最后两个原因持怀疑态度。我找不到任何信息来确认或否认有关影响某种权重的内联关键字的观点。我也无法为 class 声明中定义的函数触发 "warn if not inlined" 警告。
如果您已经读到这里,我想知道您是否对上述任何一点有任何见解?此外,如果您能指出任何相关的 articles/documentation,我将不胜感激。
谢谢!
编辑 1:添加了 LLVM(换句话说 "clang")内联代码
编辑 2:添加关于如何 "resolve" 的说明。
实际正确
第 1 点当然是不言自明的。
第 2 点是无稽之谈 - 所有现代编译器(至少是 MS、GCC 和 Clang [aka XCode])完全忽略 inline
关键字并完全基于 frequency/size 标准(根据大小 * 次数确定 "code-bloat factor",因此小函数或仅调用几次的函数更有可能被内联 - 当然 getter 将是编译器始终内联的完美选择,因为它只有两到三个指令,而且很可能比加载 this
,然后调用 getter 函数要短。
inline
关键字根本没有任何区别[并且 C++ 标准规定 class 中的定义无论如何都是 inline
]。
第 3 点是另一种可能的情况,但我认为根据其定义隐式内联这一事实应该会产生相同的结果。不久前在 Clang 邮件列表上讨论了 inline
关键字及其含义,结论是 "the compiler usually knows best".
将 inline
与虚函数一起使用通常也完全没有用,因为它们几乎总是通过 vtable 条目调用,并且不能内联。
编辑 1:
取自 LLVM 的代码 "InlineCost.cpp":
InlineCost InlineCostAnalysis::getInlineCost(CallSite CS, Function *Callee,
int Threshold) {
// Cannot inline indirect calls.
if (!Callee)
return llvm::InlineCost::getNever();
// Calls to functions with always-inline attributes should be inlined
// whenever possible.
if (CS.hasFnAttr(Attribute::AlwaysInline)) {
if (isInlineViable(*Callee))
return llvm::InlineCost::getAlways();
return llvm::InlineCost::getNever();
}
// Never inline functions with conflicting attributes (unless callee has
// always-inline attribute).
if (!functionsHaveCompatibleAttributes(CS.getCaller(), Callee,
TTIWP->getTTI(*Callee)))
return llvm::InlineCost::getNever();
// Don't inline this call if the caller has the optnone attribute.
if (CS.getCaller()->hasFnAttribute(Attribute::OptimizeNone))
return llvm::InlineCost::getNever();
// Don't inline functions which can be redefined at link-time to mean
// something else. Don't inline functions marked noinline or call sites
// marked noinline.
if (Callee->mayBeOverridden() ||
Callee->hasFnAttribute(Attribute::NoInline) || CS.isNoInline())
return llvm::InlineCost::getNever();
DEBUG(llvm::dbgs() << " Analyzing call of " << Callee->getName()
<< "...\n");
CallAnalyzer CA(TTIWP->getTTI(*Callee), ACT, *Callee, Threshold, CS);
bool ShouldInline = CA.analyzeCall(CS);
DEBUG(CA.dump());
// Check if there was a reason to force inlining or no inlining.
if (!ShouldInline && CA.getCost() < CA.getThreshold())
return InlineCost::getNever();
if (ShouldInline && CA.getCost() >= CA.getThreshold())
return InlineCost::getAlways();
return llvm::InlineCost::get(CA.getCost(), CA.getThreshold());
}
可以看出(在代码的其余部分进行了一些挖掘),只检查了 "always" 和 "never" 内联选项。 None 用于内联关键字本身。
[请注意,这是 clang 和 clang++ 的内联代码 - clang 本身在生成代码时并没有做任何特别聪明的事情,它是 "just"(让数百名为此花费数百年的程序员感到沮丧项目!)一个用于转换为 LLVM IR 的 C 和 C++ 解析器,所有好的、聪明的东西都在 LLVM 层完成——这确实是提供 "multilanguage" 编译器框架的好方法。我已经编写了一个 Pascal 编译器,尽管在编译器工作方面是一个完全的新手,但我的编译器(使用 LLVM 生成实际的机器代码)在(生成的代码的)基准测试中比 Free Pascal 更好——这都要归功于 LLVM,几乎 none 其中是我的工作——除了一些在特定基准测试中内联一些常用函数的代码]
我没有 MS 编译器的访问源(doh!),我懒得下载 gcc 只是为了检查一下。根据经验,我知道这三个函数都将内联没有 inline 关键字的函数,并且 gcc 将积极内联它可以确定只有一个调用者的函数(例如大型 static
辅助函数)
编辑 2:
解决此类问题的正确方法是制定一个编码标准,明确说明何时何地应该使用 inline
[以及 class] 中定义的函数,并且什么时候不应该这样做。如果目前,其他 classes 中的其他小 getter 函数中的 none 有 inline
,那么这个函数确实会很奇怪并且很突出。如果除某些以外的所有人都有 inline
,那可能也应该修复。
另一个轶事:我个人喜欢将 if 语句写成
if (some stuff goes here)
(space在if和括号之间,但不在里面的东西周围) 但是工作中的编码标准说:
if( some stuff goes here )
(如果和括号之间没有space,但是里面的东西space)
我不小心得到了这样的东西:
if ( some stuff goes here )
在一些待审查的代码中。我修复了该行,但决定在 if
之后用 space 修复其他 175 个 if 语句 - 该文件中总共有不到 350 个 if 语句,所以超过一半不正确...
对于您提到的特定示例,GetBar
是隐式内联的,这使得 inline
关键字变得多余。 C++ 标准的第 7.1.2 P3 节说:
A function defined within a class definition is an inline function.
另外 VC++ documentation 声明相同:
A class's member functions can be declared inline either by using the inline keyword or by placing the function definition within the class definition.
一般情况下,您不需要使用inline
关键字。一位 C/C++ 专家曾在 2002 年说过:
the inline keyword allows the programmer to tell the compiler something it might have a hard time figuring out automatically. However, in the future, compilers may be able to do a better job of making inline decisions than programmers. When that happens, the inline keyword might be regarded as a quaint reminder of when programmers were forced to worry about details of code generation.
您可以找到完整的文章 here。您应该阅读整篇文章以了解为什么将关键字添加到 C99。文章还讨论了 extern inline
函数。
现在我们处于未来,现代编译器确实非常复杂,不再需要此关键字。唯一的例外是使用 extern inline
函数时。
Does the inline keyword have any affect whatsoever on the compiler's decision to inline the function?
在 MSVC 中,inline 关键字可能会影响编译器的决定,although the compiler may choose to ignore this hint if inlining would be a net loss。
The inline keyword makes it clear to other programmers interacting with this code that these functions are/should be inlined.
这是一个无效的理由。程序员是人。对于是否内联函数,人类通常很难做出比 MSVC 更好的决定。第二,当你告诉 him/her 一个函数应该内联时,程序员应该怎么做?编译器是进行内联的那个。警告本身不会告诉您是否必须对此做任何事情,如果是这种情况,该怎么做。
Many compilers still take the inline keyword into account in this case and use it to affect (read: increase) some kind of weighting that is used to decide whether or not said function would in fact be inlined.
这在 MSVC 中是正确的。但是现代的 MSVC 不再需要这个提示了。如果内联函数有好处,编译器就会知道并内联它。如果不是,它将忽略人工插入的内联关键字。
Having the inline keyword there means that "warn if not inlined" warnings will be triggered (if enabled) if said functions are not inlined for whatever reason.
MSVC 编译器发出警告 C4710 when a function that is selected for inlining was not inlined. This warning is disabled by default because most developers don't (and shouldn't) care about this warning. You can enable this warning, however。除非您是编译器优化研究人员并且想了解 MSVC 使用的内联算法,否则您不应启用这些警告。
我忽略了问题中特定于 C++ 的部分,根据你的 #2 项目符号只关注普通内联函数(C 或 C++):
I'm skeptical about the last two reasons. I can't find any information that either confirms or denies the the point about the inline keyword affecting some sort of weighting.
https://blog.tartanllama.xyz/inline-hints/ 提供了一些细节,日期为 2018 年 1 月。简而言之:Clang+LLVM 和 GCC 在决定是否内联函数时都会考虑关键字。这不是唯一的考虑因素,但确实会影响决策。