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

德欣

假设我有以下代码:

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()inside的调用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型更新执行预计算;快速进行操作没有任何优势。

5gon12eder

一般来说,您不应尝试比编译器更聪明。现代的编译器在决定如何内联函数方面做得非常出色,而众所周知,人类对此一无所知。

以我的经验,最好的办法是将所有相关函数作为inline函数放在同一个翻译单元中,以便编译器可以看到它们的定义并可以按需内联它们。但是,是否保留将给定函数内联到编译器的最终决定是最重要的决定,并且非常谨慎地使用“强制内联”,除非您有证据表明它在给定情况下具有有益的作用。

为了简化编译器的工作,您可以向其提供有关程序的其他信息。在GCC和Clang中,您可以为此使用函数属性

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_path1以及Foo::slow_path2对小型高速缓存足迹。如果这些函数之一调用Foo::helper,它可以根据情况决定是否内联它。(有关注释的确切效果,请参见链接的手册中的文档。)

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

当然,如果您担心这些事情,请首先确保启用适当的优化级别(-O2)。尤其是繁重的C +代码(即几乎所有使用标准库或Boost的代码)在编译时都可能产生非常丑陋的机器代码,而无需进行适当的优化。还请考虑是否要将assert离子编译到您的代码(-DNDEBUG)中。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

编译器是否对所有内联函数调用都执行相同的操作?

为什么对于内联函数,编译器会尝试在每个调用点生成代码

有没有办法检查编译器是否内联C ++ lambda函数?

编译器如何内联具有分支和多个返回语句的函数?

编译器选择错误的重载函数

虚函数调用的编译器优化

Google Closure编译器错误地删除了函数调用

使用Visual Studio编译器分析内联的C ++函数

编译器不断内联函数和数组值

根据其版本有条件地选择编译器

现代Java编译器/ JVM内联函数/方法是否完全从一个地方调用?

C ++编译器如何有效地内嵌函数局部的lambda?

编译器是否更有可能在指定了inline关键字的类声明中内联函数?

内联函数在不同的翻译单元中具有不同的编译器标志未定义的行为?

没有类型转换的 Java 编译器如何在子类中调用正确的 equals() 函数?

ghci编译器优化:两次调用具有相同参数的函数

编译器选择错误的重载调用IEquatable <T> .Equals

有条件地从编译器隐藏代码

理解编译器在解决函数重载时的选择

编译器如何选择正确的重载函数?

C ++编译器选择了错误的重载函数

编译器如何确定要调用的函数模板?

编译器如何解释函数调用中的冒号?

反射调用空arg构造函数的编译器警告

编译器优化忽略循环中无效的函数调用

Roslyn编译器以零优化函数调用乘法

C ++编译器是否优化重复的函数调用?

编译器会优化构造函数调用吗

编译器可以优化多个相同的函数调用吗