今天作高性能计算机系统的做业的时候,发现gcc中的优化选项有不少应用 。html
例如对于C源码:算法
#include <stdio.h> #include <stdlib.h> int main() { int x[101],y[101]; int a,i; a = 5; for(i=0;i<=100;i++) { x[i] = i+1; y[i] = i; } for(i=100; i>=0; i--) y[i] += a*x[i]; return 0; }
一、直接用gcc main.c –S –O0进行编译,即禁止编译器进行优化,生成的汇编语言文件为:编程
.file "main.c" .def ___main; .scl 2; .type 32; .endef .text .globl _main .def _main; .scl 2; .type 32; .endef _main: pushl %ebp movl %esp, %ebp andl $-16, %esp subl $816, %esp call ___main movl $5, 808(%esp) movl $0, 812(%esp) jmp L2 L3: movl 812(%esp), %eax movl 812(%esp), %edx incl %edx movl %edx, 404(%esp,%eax,4) movl 812(%esp), %eax movl 812(%esp), %edx movl %edx, (%esp,%eax,4) incl 812(%esp) L2: cmpl $100, 812(%esp) jle L3 movl $100, 812(%esp) jmp L4 L5: movl 812(%esp), %eax movl 812(%esp), %edx movl (%esp,%edx,4), %ecx movl 812(%esp), %edx movl 404(%esp,%edx,4), %edx imull 808(%esp), %edx leal (%ecx,%edx), %edx movl %edx, (%esp,%eax,4) decl 812(%esp) L4: cmpl $0, 812(%esp) jns L5 movl $0, %eax leave ret
二、当用gcc main.c –S –O1进行编译的时候,尝试优化编译时间和可执行文件大小等,汇编程序以下:编程语言
1 .file "main.c" 2 3 .def ___main; .scl 2; .type 32; .endef 4 5 .text 6 7 .globl _main 8 9 .def _main; .scl 2; .type 32; .endef 10 11 _main: 12 13 pushl %ebp 14 15 movl %esp, %ebp 16 17 andl $-16, %esp 18 19 call ___main 20 21 movl $0, %eax 22 23 leave 24 25 ret
三、当用gcc main.c –S –O2进行编译的时候,会进行部分优化,但不进行循环展开和函数内联等操做,相比于O1的1级优化获得的汇编代码,多了一条对齐指令即.p2align 2,3;汇编程序以下:函数式编程
1 .file "main.c" 2 3 .def ___main; .scl 2; .type 32; .endef 4 5 .text 6 7 .p2align 2,,3 8 9 .globl _main 10 11 .def _main; .scl 2; .type 32; .endef 12 13 _main: 14 15 pushl %ebp 16 17 movl %esp, %ebp 18 19 andl $-16, %esp 20 21 call ___main 22 23 xorl %eax, %eax 24 25 leave 26 27 ret
四、用gcc main.c –S –O3进行优化时,会进行循环展开,分支预测,函数内联等,但与O2的2级优化获得的汇编代码同样,多是由于在O2和O3的Gcc都能识别尾递归调用并进行优化,因此在这里使用了尾调用方式,从代码中也能够看到有一条递归调用指令call main。查资料获得,实现尾递归优化的选项是-foptimize-sibling-calls,是O2新增的的功能。汇编程序以下:函数
1 .file "main.c" 2 3 .def ___main; .scl 2; .type 32; .endef 4 5 .text 6 7 .p2align 2,,3 8 9 .globl _main 10 11 .def _main; .scl 2; .type 32; .endef 12 13 _main: 14 15 pushl %ebp 16 17 movl %esp, %ebp 18 19 andl $-16, %esp 20 21 call ___main 22 23 xorl %eax, %eax 24 25 leave 26 27 ret
说到尾递归,则要说到递归调用。尾递归的wiki解释以下:性能
尾部递归是一种编程技巧。递归函数是指一些会在函数内调用本身的函数,若是在递归函数中,递归调用返回的结果总被直接返回,则称为尾部递归。尾部递归的函数有助将算法转化成函数编程语言,并且从编译器角度来讲,亦容易优化成为普通循环。这是由于从电脑的基本面来讲,全部的循环都是利用重复移跳到代码的开头来实现的。若是有尾部归递,就只须要叠套一个堆栈,由于电脑只须要将函数的参数改变再从新调用一次。利用尾部递归最主要的目的是要优化,例如在Scheme语言中,明确规定必须针对尾部递归做优化。[1][2]可见尾部递归的做用,是很是依赖于具体实现的。(http://zh.wikipedia.org/wiki/%E5%B0%BE%E9%80%92%E5%BD%92)优化
这种调用老是在函数末尾执行,而且不会用到调用函数里的任何局部变量。因此有些编译器对此进行优化,在被调用函数执行时,直接利用调用函数的堆栈,不须要从新开辟堆栈空间,因此通常不会致使递归中出现的栈溢出。而通常递归由于调用过程当中会存储局部变量,因此调用次数太多时就会发生溢出。可是并非全部编译器都会对尾递归进行优化,通常在函数式编程语言中会优化。因此当在gcc中用了-O2或者-O3优化选项以后,就会对尾递归进行优化,通常不会形成溢出,而默认的进行可能会形成溢出。能够参考这篇博文:http://www.cnblogs.com/JeffreyZhao/archive/2009/04/01/tail-recursion-explanation.html。其中说到:与普通递归相比,因为尾递归的调用处于方法的最后,所以方法以前所积累下的各类状态对于递归调用结果已经没有任何意义,所以彻底能够把本次方法中留在堆栈中的数据彻底清除,把空间让给最后的递归调用。这样的优化便使得递归不会在调用堆栈上产生堆积,意味着即时是“无限”递归也不会让堆栈溢出。spa