条件内联
编译,配置,调试等与内敛有关的消极因素会将内联的时间点尽量后移,一般都是大部分调试完之后才会考虑内联部分。理想情况是内联关系在配置的结果上的,而且大多数的编译器也可以使用编译开关来阻止内联,但有些编译器不支持的话那只能使用条件编译的方式来控制内联。
但一个隐患是配置如果修改需要频繁的在服务于性能的内联和服务于测试的外联,但我们可以使用预处理的方式避免问题的产生。
以下就是条件编译的例子
头文件定义了INLINE关键字,包含了x.inl文件;如果没有定义INLINE,内联将不会被定义,inline关键字被取消。这种方法可以灵活的控制函数的内联和外联。
选择性内联
c++最大的缺点之一就是没有选择性内联的特性,也就是说我们不能确定一个函数在某个调用下是内联还是不是内联。一旦定义了一个函数是内联的,那么这个函数在调用的时候就是内联的。
这我们可以使用一种方法解决这个问题。看下面的例子:
int x::y()
{
…
}
x.h
class x {
public:
int y();
int inline_y();
……
};
x.inl
inline
int x::inline_y()
{
……
}
x.c
int x::y()
{
return inline_y();
}
这样的话大致就给出了两种方式的实现,内联的函数inline_y,非内联的函数y。
递归内联
直接的递归方法不能进行内联,而且在调用的时候直接调用会比较消耗性能。比如二叉树的搜索,属于尾部递归,可以转换成迭代的方式。
binarytree* find(int key, binarytree* tree)
{
if(key == tree->key) {
return tree;
}
else if (key > tree->key) {
find(key, tree->right);
}
else if (key < tree->key) {
find(key, tree->left);
}
}
// 迭代
binarytree* find(int key, binaytree* tree)
{
binarytree* temp = this;
while (temp != nullptr) {
if (temp->key == key) {
return temp;
}
else if (temp->key > key) {
temp = temp->left;
}
else if (temp->key < key) {
temp = temp->right;
}
}
return nullptr;
}
以上就是将尾部递归转换成迭代的方法,一般有些优秀的编译器识别到这个尾部递归就会自动转换成迭代的方式。因此,对于一些简单得递归还是能使用迭代就使用迭代代替。
然而对于其他的递归也可以使用内联来解决递归的性能问题,但是会付出一些代码膨胀的代价。比如下面的二叉树输出id的方法。
以上只是简单进行一次内联拆解递归调用的例子,但是第一次的拆解就能减少函数调用的损耗,同样也可以进行第二次第三次递归的拆解,但是这样伴随着代码的膨胀,所以还是要自己权衡。
对静态局部变量进行内联
局部静态变量的内联需要看当前编译的编译器是否支持静态变量的内联。由于c++的机制需要保证静态变量在编译过程中都只有一个实例,因此有些编译器会在每个编译模块都生成一个独立的实例,这样是有危险的。如果在不支持内联静态变量的编译器下使用静态变量的话,需要使用静态类来提高全局的范围。
要点
内联可以提高性能:需要找程序的快速路径进行内联
条件内联可以减少内联,减少编译时间,简化了早期开发阶段的调试工作
选择性内联是指只在某些位置对方法进行内联的方式,这种方法只在性能要求很重要的代码中使用
递归内联虽然让人别扭,但是对于递归确实可以提高他的性能
要注意局部静态变量
内联针对的是消除调用开销,在使用内联之前可以先确认下机器上真正的内存调用开销。