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

时间:2016-01-05 01:57:56

标签: 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()中的默认行为。 / p>

一种解决方法是让慢路径函数定义和对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类更新执行预计算;快速做这些没有任何好处。

1 个答案:

答案 0 :(得分:4)

一般来说,你不应该试图比编译器更聪明。现代编译器在决定如何内联函数方面做了很棒的工作,而人类在推理这个方面却出了名的不好。

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

为了简化编译器的工作,您可以为其提供有关程序的其他信息。在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的代码)在编译时都会产生非常难看的机器代码而没有适当的优化。还要考虑是否要将assert离子编译到代码中(-DNDEBUG)。