假设我有以下代码:
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型更新执行预计算;快速进行操作没有任何优势。
一般来说,您不应尝试比编译器更聪明。现代的编译器在决定如何内联函数方面做得非常出色,而众所周知,人类对此一无所知。
以我的经验,最好的办法是将所有相关函数作为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] 删除。
我来说两句