浅谈编译器优化

来源:互联网 发布:python网页自动化 编辑:程序博客网 时间:2024/05/17 12:52
关于优化,可以说途径千千万万,没有一定的法则,但大抵有那么几个比较基本的法则,比如:充分了解计算机的体系结构,了解自己所处理的数据的特点,熟悉编译器的工作原理等等。今天我想谈谈关于编译器方面的一些见解,编译器分为预处理,编译,链接等大概的几个部分,如果能够清楚这几个阶段编译器各做了什么事情,编译器在哪方面比较聪明,能够替你做一些优化,又在哪方面比较弱智,无法替你做优化,你就能很清醒地知道,你做的哪些优化是有效的,下面举例说明:


今天有人提出在汇编指令中,去掉一些地址偏移乘法计算,这种善于动脑筋的方法值得赞赏,但可能对编译过程理解不是很透彻,会造成认识上的一些偏差。

比如如下指令:
movq 8*4(%%rsi),%%rax
这是一个移动数据的指令,操作的对象是一个128位的数,其中源操作数的选址方式是寄存器间接寻址,地址偏移量是8*4,目的操作数是一个128位的普通寄存器。很显然,源操作数有一个地址偏移计算的过程,其过程常规理解应该是,首先计算8*4,得到32,然后将32加到地址寄存器%%rsi中去,然后再去执行寻址内存的操作,那么按照这个理解,我们想当然可以提出如下优化:
movq 32(%%rsi),%%rax
很显然这个过程少了一个计算8*4=32的过程,应该能起到优化作用,但果真如此吗??答案是否定的,这种替换没有任何优化作用,反倒代码可读性可能降低,这是为什么呢?原因是我们混淆了操作符的执行时期,我们一直在说,new/delete 是操作符,sizeof是操作符+ - * / %是操作符,但我们是否真正理解了什么是操作符吗,那些操作符操作是在编译期执行的,根本没有转化成指令,那些操作符操作最终是要转化为执行期的指令呢,我们不妨举个例子:
假设有如下C语言函数
int main(){    int a;    int b;    a = 3 * 5 - 2 + 1;    b = a + 9 * 6 + 1;    return 0;}

执行

gcc -S compile.c 

得到如下compile.s汇编代码:

movl $14,-8(%%ebp)movl -8(%%ebp),%%eaxaddl $55,%%eaxmovl %%eax,-4(%%ebp)

其中

-8(%%ebp),-4(%%ebp)

分别代表a,b变量在内存中的地址,很显然,经过一次编译已经完成了如下操作:

3 * 5 - 2 + 1 = 149 * 6 + 1 = 55
换句话说这些操作根本没有转化为执行期的代码,当然也就无所谓效率提高不提高了
其实主函数内赋值操作的代码完全等同于:
a = 14;b = a + 55;

那到底那些代码能够最终转化为执行期间的代码呢,一个原则,就是操作符其中的一个操作对象是内存中的值,比如,a = 14 是一个赋内存变量值操作,最终转化为了

movl $14,-8(%%ebp)

指令.

而在b = a + 55 中,加法操作转化为
movl -8(%%ebp),%%eaxaddl $55,%%eax
即:首先去取内存变量a的值,然后完成加法操作
而赋值操作转化为:
movl %%eax,-4(%%ebp)
将最终的和值送给内存变量b所在的地址。

由上可知,懂得编译器的工作原理,对优化工作来说还是比较重要的