0xAA55 发表于 2015-10-28 01:39:24

论inline关键字与_asm内联汇编对程序速度的优化效果

inline关键字本来是C++的,后来微软把它弄成.c后缀的文件也能用的关键字,意在提示编译器:请把这个函数内联到调用它的地方!
所谓内联函数,就是将函数本身的内容直接展开到调用它的地方,因为每次调用函数,CPU都要执行一个CALL指令来跳转到要调用的函数的入口,然后运行函数指令。内联的过程可以省略掉这个调用的过程,直接让编译器给你把要调用的函数的指令给你展开到用它的地方。看起来好处是显而易见的,然而实际情况是怎样的呢?

我们先看CPU运行一个指令需要多长时间?这要看这个指令占多少个时钟周期。时钟是什么?我们买CPU的时候,有个“主频”,比如我的一台电脑的CPU是i5的,主频是3.2 GHz,这是怎样的一个速度呢?1 GHz = 1000 MHz,1 MHz = 1000 KHz,1 KHz = 1000 Hz,而1000 Hz就是“每秒钟一千次”。
3.2 GHz = 3.2 x 1000 x 1000 x 1000 = 每秒钟3200000000个时钟周期。
x86平台PC平均每条CPU指令所需时钟数,我们就暂且算作平均每个指令2个周期。那么这相当于这个CPU每个核心每秒钟能运行1600000000个指令。

这么说是不是如果所有的函数都用内联的话,CPU是不是就可以少运行一些看起来没用的指令(比如CALL)来提高效率呢?然而别忘了CPU是从内存读取指令来运行的。内存的频率并不和CPU相同,举个例子,还是我那台电脑,它的内存是DDR3的,频率是1600 MHz。内存频率是啥?CPU每秒钟可以读写它的次数。每次读写需要至少一个内存时钟周期,而且每次读写的字节数非常有限。为了提升CPU的实际效率,现在的x86的CPU都有指令缓存,也就是CPU自己自带了一块高速内存,专门用于存储指令等数据。这块高速缓存的速度就比外设的内存要高很多,但是容量也非常有限,从一级缓存往下,容量依次从几KB到几十MB不等。

你的程序的指令第一次被运行的时候,运行速度相当于CPU读内存的速度,并不能达到CPU本身支持的最高速度。运行过一次的指令会被存储进缓存,缓存满了就会把之前存入缓存的指令删掉。随着CPU的运行,越来越多的指令就被缓存进CPU了。CPU执行缓存中的指令比执行没缓存过的指令要快很多。假设我们的程序中一个很长的函数被内联了,而这个函数被使用的频率还不低,那么我们就会编译出一个非常大的程序,这个程序被运行的时候,CPU其实并没有什么机会来重复利用缓存中的指令,运行速度并不理想。在AMD的机器上反映十分明显,我见过一个AMD的机器,CPU是4 GHz的,但是内存频率仅有333 MHz,没有被缓存的指令只有333 MHz的速度,而被缓存的却有4 GHz的速度。因此滥用inline关键字对函数进行内联并不能提升程序的运行速度。事实上inline关键字也只是个请求,请求编译器对其进行内联处理,并不是所有的编译器都会傻傻地去内联。典型的就有VS的编译器,它还有个__forceinline关键字,意思是“这个真的要内联!”(此所谓强制内联),然而该不内联的时候它也确实不给你内联。

内联这个技术也不是卵用都没有,对于面向对象的情况,经常会遇到传说中的"get"和"set",它们并不一起出现。比如下面这种。class foo
{
protected:
    int credits;
public:
    foo()
    {
      credits = 0;
    }

    int recharge(int num)
    {
      credits += num;
    }

    int purchase(int num)
    {
      if(credits >= num)
      {
            credits -= num;
            return num;
      }
      else
            return 0;
    }

    int getcredits()
    {
      return credits;
    }
}这样的类,有很多成员方法(函数)的语句只有一两条。像这样的函数就有内联的价值了,因为把它们单独缓存起来并不划算。
然而就算是这样的情况,我们也可以不使用inline关键字,VS的编译器自动会将这些超简短的函数进行内联。所以inline关键字多半没什么卵用,VS编译器编译参数就有“完全不内联”、“只内联有inline关键字的”、“条件合适的都内联”这几个选项。到底该不该内联?也许编译器比你还清楚。

说完这个我们来说内联汇编。内联汇编虽然也带了“内联”两个字,但是这里说的和上面的不一样。内联汇编,是让用户自己使用_asm关键词,将汇编指令插入到C函数内。听起来很高大上,然而VS取消了x64平台对内联汇编的支持。

没错哈。内联汇编本来就是跨平台能力最弱的一种编码方式。而且论优化的话,假设你用的是VS2012,开满了优化,那么实际生成的指令的优化效果,恐怕要比程序员自己写汇编好的多得多。除非你需要使用sse,sse2,mmx等指令集,否则能不内联汇编就不要内联汇编了。

从跨平台上来说,先说x86平台,不同编译器内联汇编的语句是不一样的。VS使用的是_asm关键字,编写masm汇编语句,而gcc则使用__asm__("指令")的方式内联汇编,使用的是gas的语句(吐槽一下gas各种反人类,比如movl %eax,%ecx,你大概要醉了)。这种对平台依赖特别大的程序用C或者C++这种跨平台语言来写可真是浪费。因此还是乖乖写好C和C艹本身的语句比较好。

这里吐槽一下国产的那堆流氓软件(比如什么酷狗音乐之类的玩意儿、还有腾讯的各种游戏以及QQ等软件)的作者觉得大家的CPU其实都相当快了,因此他们对于程序优化和用户体验并不重视,于是实际用下来,你总是会吐槽“这电脑好卡……”



0x01810 发表于 2015-10-28 07:48:30

这么说来递归就没必要优化了?

7KY6 发表于 2018-1-14 15:27:29

可以可以!!
页: [1]
查看完整版本: 论inline关键字与_asm内联汇编对程序速度的优化效果