关于函数内联的一些理解

最近在看提高C++性能的编程技术一书,居然发现里面有三章是专门讲述内联的。看来内联在作者心中占据了非常重要的位置,内联在作者心里是最有效的改进性能的策略了。

阅读完这三章内容之后,我对内联的理解更加模糊了。因为到最后作者也说你内联性能不一定变好,你不内联性能也不一定变坏。能跟内联牵扯上关系的东西太多了。

我记得有人曾经问过我这个问题,内联和宏替换的区别是什么。当时我还是大三的时候,准备找个实习,是电面的时候被别人问到的。还是国内著名的IT企业,我就这样的水水的跟人家乱扯。现在回忆起来我好像什么也没说出来的样子。说实话,我那个时候感觉内联和宏替换区别不大,一样的对待也没什么大不了的。第三次电面的时候我就彻底悲剧了,最主要的原因还是因为C++功底不扎实。想想那个时候也挺可爱的,把简历写的那么好笑那么烂,他们居然也给我机会进到最后一轮。最后一轮我实在表现的太烂了。

那么区别到底是什么了。我说说现在的理解吧。首先,宏替换不能算编译器的时候,应该算预处理器的工作,而内联属于编译器的工作,阶段都不同了。其次,宏替换很白痴就是简单的应用些规则进行代码替换而已,而内联是很高级的功能吧,inline只是对编译器的指示,内联不内联是编译器自己的决定,有可能你不写inline函数也被内联了,不过我觉得可能性比较小,而且内联不仅仅是在调用位置替换上被调用函数的代码,编译器还会对替换之后的新代码整个进行优化,也就是能够跨越函数调用进行优化,这一点据说是内联提高性能的关键。最后,宏的可读性很差,什么机制都没有,而内联就不同,函数还是函数,参数和返回类型,编译器对函数的检查等等都还在,而宏就没有这些优待。我能说出来的也就这么些了。。。

关于什么是内联的话应该就不需要我进行解释了。内联能避免函数调用的代价这一点也不需要解释了。函数调用的代价说简单点就是保存现场,主要是一些寄存器的内容,一般会把这些寄存器的内容压栈保存起来,还有就是传递参数,参数的传递是通过改变堆栈指针达到的。函数调用完成之后还需要恢复现场,就是从栈里面恢复一些寄存器的内容,还有恢复原来的栈指针。但是,具体起来函数调用需要做些什么还是比较难说清楚的,不同的体系结构做的事情都不一样,再说写c++代码的时候谁又想过多的考虑底层具体发生了什么了。

那么来思考下为什么要使用内联和为什么又不要使用内联吧。大家都知道函数调用会造成一定的代价,既然内联可以避免这个代价那么为什么我们不用内联了。好吧,那么我们就用内联把函数调用的代价消去。那么为什么我们只内联比较小的函数了,而超过一定规模的函数就不内联了。首先,如果一个函数规模较大,那么函数调用的代价在整个代价里面的比例就较小了。其次,内联较大规模的函数会造成编译后的二进制代码增大,如果函数非叶子位置函数(只会被别的函数调用的函数)那么这个可能性更大,很可能就会到达无法忍受的地步,内联造成编译时间变长也是一方面。最后,内联也会损失某种性能,比如缓存和页交换之类的,如果是函数调用,机器可以运用指令和数据缓存之类的,但是如果是内联,那么不同的调用位置其在映像里面的位置是不一样的,那么机器是不会缓存的。所以,内联也不是能随便用的。

其实说了这么多了,我们还是不知道什么时候该内联什么时候不该内联。我们只了解到,如果函数足够小就内联吧,那么小的标准是什么,代码五行以内没有循环?什么时候不该内联,函数规模太大了?反正内联是一件太纠结的事情了,有可能你用编译器测试一下内联和不内联性能是没有变化的,因为可能编译器在幕后就内联了。

根据我所看的这本书的说法,我有一些建议。对少于五行的代码和调用频率大于80%的函数一定内联,其余的你还是仔细考虑下吧。你可以再分解调用频率大于80%的函数,使其规模变小,然后再考虑是否内联它。其实,这本书的思想还是对整个系统进行测试之后才进行内联选择,毕竟性能优化也是最后阶段的事情。你需要找出系统执行的关键路径,其实也就是执行频率最高的一些函数,根据整个系统的流程图,你就会发现一条系统执行频率最高的路径,内联应该只发生在这条路径上面。

好了,这就是我对内联的一些理解,这本书关于内联的内容实在太多了,我觉得还是下次再写一篇文章扯扯吧。