我们有一个用于在通用实用程序库中发出错误信号的宏,如下所示:
#define OurMacro( condition ) \
if( condition ) { \
} else { \
CallExternalFunctionThatWillThrowAnException( parametersListHere ); \
} \
我所说的
parametersListHere
是一个以逗号分隔的常量和宏列表,由编译器在每次宏扩展时填充。
该函数调用始终解析为调用 - 函数实现不会暴露给编译器。该函数有六个参数,在调试配置中所有参数都具有有意义的值,而在发布配置中只有两个参数具有有意义的值,其他参数传递相同的默认值。
通常情况下条件成立,所以我不关心调用有多快,我只关心代码膨胀。调用具有 6 个参数的函数需要 7 个 x86 指令(6 个
push
和 1 个 call
),如果将函数签名更改为仅具有两个参数,则显然可以避免其中 4 个 push
- 这可以是通过引入一个中间“门”函数来完成,其实现对编译器来说是不可见的。
我需要评估是否应该坚持这一改变。到目前为止,我期望的主要改进是,减少参数数量将在每次调用时删除 4 条指令,这意味着围绕宏扩展的代码将变得更小,编译器将更有可能内联它并进一步优化发出的代码。
如果不实际尝试并重新编译所有代码并仔细分析发出的代码,我如何才能估计这一点?每次我读到
inline
时,都会有这样一句话:编译器决定是否内联该函数。
我能看到一些关于函数内部如何影响编译器内联决策的精确规则吗?
GCC 有一组相当大的选项,公开了他们的流程如何工作,记录在here。这当然不准确,因为它会随着时间的推移而调整,并且它依赖于 CPU。
第一条规则是“它们的主体小于预期的函数调用代码”。 第二条规则是“静态函数调用一次”。
还有一些影响 inling 过程的参数,例如
max-inline-insns-single
。 insn
是 GCC 编译器中的伪指令,在这里用作函数复杂性的度量。参数 max-inline-insns-auto
的文档清楚地表明,手动声明函数 inline
可能会导致它被考虑内联,即使它对于自动内联来说太大。
内联不是一个全有或全无的过程,因为有一个
-fpartial-inlining
标志。
当然,不能孤立地考虑内联。公共子表达式消除 (CSE) 使代码更简单。这是一个优化过程,可以使函数小到足以内联。内联后,可能会发现新的公共子表达式,因此应再次运行 CSE 遍,这反过来可能会触发进一步的内联。而且 CSE 并不是唯一需要重新运行的优化。
有关哪些函数内联以及在什么条件下(例如选定的优化级别)内联的规则特定于每个编译器,因此我建议您检查编译器的文档。但是,仅转发到另一个函数(如您建议的那样)的函数应该是任何支持它的编译器内联的良好候选者。
某些编译器有一种机制,您可以通过该机制标记您确实想要内联函数,例如MSVC++ 有 __forceinline。
如果您使用的是 Visual C++,则可以使用 __forceinline 来强制编译器内联函数。