C ++如何确定函数是否具有内联能力并且实际上是?

C++ How to determine if function has capability for being inlined and actually was?

我对 C++ 中的 inline 函数有疑问。我知道类似的问题已经出现过很多次了。我希望我的有点不同。

我知道,当您将某个函数指定为 inline 时,它对编译器来说只是一个 "suggestion"。所以万一:

inline int func1()
{
    return 2;
}

Some code later

cout << func1() << endl; // replaced by cout << 2 << endl;

所以这里没有什么神秘之处,但是像这样的情况呢:

inline int func1()
{
    return 2;
}

inline int func2()
{
    return func1() * 2;
}

inline int func3()
{
    return func2() * func1() * 2;
}

And so on...

这些函数中的哪些有机会成为内联的,它是否有益以及如何检查编译器实际做了什么?

Which of these functions have a chance to become inlined

如果执行内联的工具(1) 可以访问函数的定义(= 函数体)...

is it benefitial

...并认为这样做是有益的。如今,优化器的工作是确定内联的意义所在,对于 99.9% 的程序,程序员能做的最好的事情就是远离优化器。剩下的几个案例是像 Facebook 这样的程序,其中 0.3% 的性能损失是 huge 回归。在这种情况下,手动调整优化(连同分析、分析、分析)是可行的方法。

how to check what compiler actually did

通过检查生成的程序集。每个编译器都有一个标志,使其以 "human-readable" 格式输出程序集,而不是(或除此之外)二进制格式的目标文件。


(1) 通常,此工具是编译器,内联是编译步骤的一部分(将源代码转换为 assembly/object 文件)。这也是为什么您可能需要使用 inline 关键字来实际允许编译器内联的唯一原因:因为函数的定义必须在正在编译的翻译单元(=源文件)中可见,而且经常这意味着将函数定义放入头文件中。如果没有 inline,如果头文件包含在多个翻译单元中,这将导致多重定义错误。

请注意,编译并不是可以进行内联的唯一阶段。当您启用全程序优化(也称为 Link-时间代码生成)时,一旦创建了所有目标文件,就会在 link 时间再进行一次优化。此时,inline 关键字完全无关紧要,因为 linking 可以访问所有函数定义(否则二进制文件不会 link 成功)。因此,这是 从内联中获得最大好处的方法,而无需在编写代码时考虑它。缺点是时间:WPO 需要时间 运行,对于大型项目,可以将 link 时间延长到不可接受的水平(我个人经历过一个有点病态的案例,启用 WPO 需要程序的 link 时间从 7 分钟减少到 46).

认为 inline 只是对编译器的 提示 ,有点像 register 旧版本的 C++ 和 C 标准。警告,register 正在被废弃(在 C++17 中)。

Which of these functions have a chance to become inlined, is it benefitial

相信您的编译器 可以做出明智的内联决定。为了使某个特定的调用发生,编译器需要知道被调用函数的主体。您不应该关心编译器是否内联(理论上)。

实际上,使用 GCC 编译器:

  • inlining is not always improving the performance (e.g. because of CPU cache issues, TLB, branch predictor,等等等等....)

  • 内联决策取决于 批次 optimization options-O3-O1 更有可能发生;有许多大师选项(如 -finline-limit= 和其他)来调整它。

  • 请注意各个调用是否内联。很可能第 123 行的某些调用事件如 foo(x) 是内联的,但另一个调用事件(对 same 函数 foo)如 foo(y) 在其他一些地方,如第 456 行没有内联。

  • 调试时,您可能希望禁用内联(因为这使调试更方便)。这可以通过 -fno-inline GCC 优化标志(我经常将其与 -g 一起使用,它要求调试信息)。

  • always_inlinefunction attribute"forces"内联,noinline阻止它。

  • 如果编译和link时使用link时间优化 (LTO)例如 -flto -O2(或 -flto -O3),例如在 Makefile 中使用 CXX=g++ -flto -O2,内联可以发生在多个翻译单元(例如 C++ 源文件)之间。然而,LTO 至少使编译时间加倍(而且通常更糟)并在编译期间消耗内存(那么最好有大量 RAM),并且通常只提高几个百分点的性能(除了这个经验法则的奇怪例外) .

  • 您可能会以不同的方式优化函数,例如#pragma GCC optimize ("-O3")function attributeoptimize

  • 另请查看 profile-guided optimizations with instrumentation options like -fprofile-generate and latter optimizations with -fprofile-use with other optimization flags

如果您对哪些调用是内联的(有时,有些不会)感到好奇,请查看生成的汇编程序(例如,使用 g++ -O2 -S -fverbose-asm 并查看 .s 汇编程序文件),或者使用一些内部 dump options.

代码的可观察行为(性能除外)不应取决于编译器做出的内联决定。换句话说,不要指望内联发生(或不发生)。如果您的代码在有或没有一些优化的情况下表现不同,则很可能存在错误。所以请阅读 undefined behavior

另请参阅 MILEPOST GCC 项目(使用机器学习技术进行优化)。