使用Intel编译器(0)基础(1)内联

来源:互联网 发布:我的世界双持js 编辑:程序博客网 时间:2024/05/17 10:41
参考手册:

http://software.intel.com/sites/products/documentation/studio/composer/en-us/2011Update/compiler_c/index.htm


说明:本系列文章为个人笔记,如有不正确之处,请参考官方相关文档,如果错误发现,我会尽量更新修改。另外,以下内容不保证对于所有版本的编译器都正确,编译器的实现也可能有一些变化之处,具体参考官方文档。


更多说明请参考http://blog.csdn.net/gengshenghong/article/details/7034748中补充说明部分。


参考:

wiki:http://en.wikipedia.org/wiki/Inline_expansion


(1) 什么是内联(inlining)

In computing, inline expansion, or inlining, is a manual or compiler optimization that replaces a function call site with the body of the callee.

内联或内联展开,一般编程人员都可以理解的。简单理解,就是将函数调用用函数体代替,主要优点是省去了函数调用开销和返回指令的开销,主要缺点是可能增大代码大小。内联,可以由语言特性来完成,也可以是编译器优化来完成。


(2) 误区

内联一定会更高效吗?内联一定会使代码大小增大吗?答案是:否。

内联不一定使代码大小增大,这是比较容易理解的,假如某一个函数只被调用了一次,那么内联当然不会使得代码大小增大,当然,一般情况下,内联都会增大代码。

内联不一定会更高效,这个倒是容易误解。用函数体代码了函数调用,一般来说,不至于会降低程序性能的。但是,由于内联增大了代码,可能会导致代码在cache加载的内容不合适,就有可能导致更多的cache misses


(3)内联的性能提升

更具体来说,内联的性能提升,表现在:

1. 省去函数调用开销和返回指令开销。

2. 消除分支,并保持要执行的代码在内存中靠近,引用地址靠近从而提升指令cache的性能。这一点,不要被这里的“分支”误导了,通常来说,调用一个函数的时候,控制流被转换到其地址,可以通过分支(如jmp)或call指令,这就是这里的分支的含义。当然,在一般的C/C++中,貌似没怎么看到把函数调用为跳转指令的。总之,这一点强调的是,内联使得要调用的函数对应的指令和当前执行的指令地址靠近,从而能更有效的利用指令的cache,如果不使用内联,可能要到很”远“的一个地址去执行,有可能需要刷新cache等。

3. 进行内联后,有可能对其它的优化有好处。一个简单的例子,被内联的函数可能需要一个参数,利用参数进行if/else判断,内联后,很可能传递的常量参数会使得判断条件为永远false或true,那么编译器就可以进一步优化,去掉if/else的判断。比如下面的例子:

int ifelse(int condition) {if(condition == 0) {return 0;} else if(condition == 1) {return 1;} else {return 2;}}int foo() {return ifelse(0) + ifelse(1);}int foo_inline() {return 1;}
这里,内联之后,编译器完全可以进行上面的简化,直接返回1。


(4)C/C++中的内联和编译器的内联

在C/C++中,我们可以使用关键字inline来告诉编译器,建议编译器进行内联,这也是大部分人所了解到的。其实,更多的情况下,编译器会自己进行内联,内联对于编译器更重要。为何?主要是内联之后的代码,对于编译器优化会带来很多方便,这些内容就涉及到更多的编译器优化的知识了。Wiki上给出了例子并进行了分析。


(5)何时进行内联

编译器在编译时和链接时都是可以进行内联的,另外,运行时系统(runtime)其实也可以完成内联的操作。编译器可以在抽象语法树或中间表达式中完成内联的操作,这是编译时内联的实现方式。链接器也可以在链接时内联,这也是很容易理解的,那么一般来说,比如,对于有些库函数进行内联,其是没有提供源代码的,所以只能在链接时内联。运行时内联一般是利用运行时的运行行为信息进行反馈,编译器根据反馈信息来更好的进行内联。


(6)编译器内联优化举例

一般的编译器都多少有一些内联的优化选项,由于优化是很多其它优化的基础。当然,不同的编译器优化的结果肯定也是不一样的,取决于编译器的“智能”程度了。下面的例子用最基础的O1选项说明一下VS编译器对函数的自动内联:

// File: test.cpp// cl test.cpp /O1 /FA /cint foo(int a, int b){return a + b;}int bar(int a, int b){return a + b;}int test(int a, int b){int a1 = foo(a, b);int b1 = bar(a, b);return bar(a1, b1);}
编译后,得到test函数部分汇编片段如下:

?test@@YAHHH@Z PROC; test, COMDAT; Line 17leaeax, DWORD PTR [rcx+rdx]addeax, eax; Line 18ret0?test@@YAHHH@Z ENDP
可见,其中已经没有了foo和bar的身影,都已经被内联了。如果使用/Od编译,得到的test部分如下:

?test@@YAHHH@Z PROC; test; Line 14$LN3:movDWORD PTR [rsp+16], edxmovDWORD PTR [rsp+8], ecxsubrsp, 56; 00000038H; Line 15movedx, DWORD PTR b$[rsp]movecx, DWORD PTR a$[rsp]call?foo@@YAHHH@Z; foomovDWORD PTR a1$[rsp], eax; Line 16movedx, DWORD PTR b$[rsp]movecx, DWORD PTR a$[rsp]call?bar@@YAHHH@Z; barmovDWORD PTR b1$[rsp], eax; Line 17movedx, DWORD PTR b1$[rsp]movecx, DWORD PTR a1$[rsp]call?bar@@YAHHH@Z; bar; Line 18addrsp, 56; 00000038Hret0?test@@YAHHH@Z ENDP
很容易看到call foo,call bar这样的指令,显然是需要进行函数调用。


总结:这里只是简单的从"表面"来理解了一下内联,关于更多的和编译器实现有关的内容,不属于这里讨论的范围。


原创粉丝点击