c++ - 建议编译器有选择地内联函数调用

标签 c++ inline

假设我有以下代码:

struct Foo {
  void helper() { ... }
  void fast_path() { ...; helper(); ... }
  void slow_path1() { ...; helper(); ... }
  void slow_path2() { ...; helper(); ... }
};

fast_path() 方法对性能至关重要,因此应尽一切(合理的)努力使其尽可能快。 slow_path1()slow_path2() 方法不是性能关键。

根据我的理解,典型的编译器可能会查看此代码并决定不内联 helper() 如果它足够复杂,以减少总指令大小,如 helper( ) 在多个方法函数之间共享。如果慢速路径方法不存在,同一个编译器可能会内联 helper()

鉴于我们期望的性能特征,我们希望编译器将对 helper() 的调用内联到 fast_path() 中,但更喜欢编译器在 中的默认行为slow_path1()slow_path2()

一种解决方法是让慢路径函数定义和对 fast_path() 的调用存在于单独的编译单元中,这样编译器永远不会看到 helper()< 的用法fast_path() 共享。但保持这种分离需要特别小心,不能通过编译器强制执行。此外,文件(Foo.h、FooINLINES.cpp,现在还有 Foo.cpp)的激增是不可取的,而且额外的编译单元使构建原本可能仅包含头文件的库变得复杂。

有没有更好的办法?

理想情况下,我想要一个新的 do_not_inline_function_calls_inside_me c++ 关键字,我可以像这样使用它:

  do_not_inline_function_calls_inside_me void slow_path1() { ... }
  do_not_inline_function_calls_inside_me void slow_path2() { ... }

或者,inline_function_calls_inside_me 关键字,如下所示:

  inline_function_calls_inside_me void fast_path() { ... }

请注意,这些假设的关键字装饰了 *_path*() 方法,而不是 helper() 方法。

您可能有此类性能需求的示例上下文是编程竞赛,其中每个参与者都编写一个应用程序来监听类型 A 和 B 的稀疏全局数据广播。当接收到类型 B 广播时,每个应用程序必须执行根据先前广播的A类消息的顺序进行计算,并将计算结果提交给中央服务器。每个 B 类广播的第一个正确响应者得分。计算问题的性质可能允许对 A 类更新执行预计算;快速完成这些没有任何优势。

最佳答案

一般来说,你不应该试图比编译器更聪明。现代编译器在决定如何内联函数方面做得非常出色,而人类在这方面的推理能力是出了名的差。

根据我的经验,你能做的最好的事情就是将所有相关函数作为 inline 函数放在同一个翻译单元中,这样编译器就可以看到它们的定义并且可以在它认为合适的时候内联它们。然而,是否将给定函数内联给编译器的最终决定权交给编译器,并非常谨慎地使用“强制内联”,除非您有证据表明它在给定情况下具有有益效果。

为了使编译器的工作更轻松,您可以向它提供有关您的程序的附加信息。在 GCC 和 Clang 中,您可以使用 function attributes为此。

struct Foo {
  void helper();
  void fast_path()  __attribute__ ((hot));
  void slow_path1() __attribute__ ((cold));
  void slow_path2() __attribute__ ((cold));
};

inline void Foo::helper()     { … }
inline void Foo::fast_path()  { … }
inline void Foo::slow_path1() { … }
inline void Foo::slow_path2() { … }

这将提示编译器更积极地优化 Foo::fast_path 以提高速度,并优化 Foo::slow_path1Foo::slow_path2缓存占用空间小。如果这些函数中的任何一个调用 Foo::helper,它可以根据具体情况决定是否内联它。 (有关注释的精确效果,请参阅链接手册中的文档。)

提示编译器的更好方法是为它提供实际的分析数据。使用 GCC,您可以使用 -fprofile-generate 选项编译您的程序。这将使用收集配置文件统计信息的代码来检测您的二进制文件。现在用一组有代表性的输入运行你的程序。这样做将创建一个包含收集到的配置文件数据的 *.gcda 文件。现在使用 -fprofile-use 选项重新编译。 GCC 将使用收集到的配置文件信息来确定代码中的哪些路径是热路径以及它们如何相互交互。这种技术被称为配置文件引导优化 (PGO)。

当然,如果您担心此类事情,请首先确保启用适当的优化级别 (-O2)。尤其是大量使用模板的 C+ 代码(即,几乎所有使用标准库或 Boost 的代码)在未经适当优化的情况下编译时会生成非常丑陋的机器代码。还要考虑是否要将 assertion 编译到您的代码中 (-DNDEBUG)。

关于c++ - 建议编译器有选择地内联函数调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34603041/

相关文章:

c++ - 与 OpenCV 和 MinGW 的 asmlibrary

c++ - CField 类成员函数的 IsDirty 名称

generics - F# 泛型类型重载运算符

c++ - "inline"是否隐含在类定义中定义的 C++ 成员函数中

html - 如何在单行中使用列表标签

没有缩进的 HTML 项目符号点

c++ - 四舍五入到最接近的数字倍数

c++ - C++ 是否支持 'finally' block ? (我一直听到的这个 'RAII' 是什么?)

c++ - g++模板问题

c++ - 头文件包含函数体,会导致重复定义吗?