GCC的优化效果测试

来源:互联网 发布:中华魔力迅白牙膏知乎 编辑:程序博客网 时间:2024/06/04 19:23
有这么个程序:
#include <stdio.h>int main(int argc, const char *argv[]){int a;scanf("%d", &a);if (a + 1 == a){printf("bad!:(\n");}else{printf("good!:)\n");}return 0;}




可以看得出,a + 1 == a在一般情况下是不可能成立的,编译后得到结果:

.file"deadloop1.c".section.rodata.str1.1,"aMS",@progbits,1.LC0:.string"%d".LC1:.string"good!:)".text.p2align 4,,15.globl main.typemain, @functionmain:pushl%ebpmovl%esp, %ebpandl$-16, %espsubl$32, %espleal28(%esp), %eaxmovl%eax, 4(%esp)movl$.LC0, (%esp)call__isoc99_scanfmovl$.LC1, (%esp)callputsxorl%eax, %eaxleaveret.sizemain, .-main.ident"GCC: (Debian 4.4.5-8) 4.4.5".section.note.GNU-stack,"",@progbits




果然,gcc是知道a不会等于a+1的。于是这激起了我的好奇心,我又想继续为难gcc,于是程序改为:

#include <stdio.h>int inc(int num){return num + 1;}int main(int argc, const char *argv[]){int a;scanf("%d", &a);if (a + 1 == inc(a)){printf("good!:)\n");}else{printf("bad!:(\n");}return 0;}



编译得到:
.file"deadloop.c".text.p2align 4,,15.globl inc.typeinc, @functioninc:pushl%ebpmovl%esp, %ebpmovl8(%ebp), %eaxpopl%ebpaddl$1, %eaxret.sizeinc, .-inc.section.rodata.str1.1,"aMS",@progbits,1.LC0:.string"%d".LC1:.string"good!:)".text.p2align 4,,15.globl main.typemain, @functionmain:pushl%ebpmovl%esp, %ebpandl$-16, %espsubl$32, %espleal28(%esp), %eaxmovl%eax, 4(%esp)movl$.LC0, (%esp)call__isoc99_scanfmovl$.LC1, (%esp)callputsxorl%eax, %eaxleaveret.sizemain, .-main.ident"GCC: (Debian 4.4.5-8) 4.4.5".section.note.GNU-stack,"",@progbits



明显不可能到的分支又被优化掉了,而且这次竟然是跨函数的优化,太强大了!
后来想想,也许是gcc自动把那个很短的函数内联了,才导致被发现并被优化掉,于是程序改成:
#include <stdio.h>int __attribute__((noinline))inc(int num){return num + 1;}int main(int argc, const char *argv[]){int a;scanf("%d", &a);if (a + 1 == inc(a)){printf("good!:)\n");}else{printf("bad!:(\n");}return 0;}




编译得到的结果是:


.file"deadloop.c".text.p2align 4,,15.globl inc.typeinc, @functioninc:pushl%ebpmovl%esp, %ebpmovl8(%ebp), %eaxpopl%ebpaddl$1, %eaxret.sizeinc, .-inc.section.rodata.str1.1,"aMS",@progbits,1.LC0:.string"%d".LC1:.string"good!:)".LC2:.string"bad!:(".text.p2align 4,,15.globl main.typemain, @functionmain:pushl%ebpmovl%esp, %ebpandl$-16, %esppushl%ebxsubl$44, %espleal28(%esp), %eaxmovl%eax, 4(%esp)movl$.LC0, (%esp)call__isoc99_scanfmovl28(%esp), %ebxmovl%ebx, (%esp)addl$1, %ebxcallinccmpl%eax, %ebxje.L8movl$.LC2, (%esp)callputsaddl$44, %espxorl%eax, %eaxpopl%ebxmovl%ebp, %esppopl%ebpret.p2align 4,,7.p2align 3.L8:movl$.LC1, (%esp)callputsaddl$44, %espxorl%eax, %eaxpopl%ebxmovl%ebp, %esppopl%ebpret.sizemain, .-main.ident"GCC: (Debian 4.4.5-8) 4.4.5".section.note.GNU-stack,"",@progbits



这下明白了,gcc果然是把它内联了才能进行进一步优化。从这里得知内联的好处不仅仅是省去了切换栈帧,更重要的是还可以把两个函数的代码放在一起进行进一步优化。


能不能继续挑战一下gcc?写出如下程序:


#include <stdio.h>#include <unistd.h>#include <time.h>int main(int argc, const char *argv[]){time_t t1, t2;time(&t1);sleep(3);time(&t2);if (t1 != t2){printf("good!:)\n");}else{printf("bad!:(\n");}return 0;}




很明显,获取了时间t1后,休息3秒,再获取一次时间t2,两次时间一般情况下应该会不同,但是编译出来的结果确是:
.file"deadloop2.c".section.rodata.str1.1,"aMS",@progbits,1.LC0:.string"good!:)".LC1:.string"bad!:(".text.p2align 4,,15.globl main.typemain, @functionmain:pushl%ebpmovl%esp, %ebpandl$-16, %espsubl$32, %espleal28(%esp), %eaxmovl%eax, (%esp)calltimemovl$3, (%esp)callsleepleal24(%esp), %eaxmovl%eax, (%esp)calltimemovl28(%esp), %eaxcmpl24(%esp), %eaxje.L2movl$.LC0, (%esp)callputsxorl%eax, %eaxleaveret.p2align 4,,7.p2align 3.L2:movl$.LC1, (%esp)callputsxorl%eax, %eaxleaveret.sizemain, .-main.ident"GCC: (Debian 4.4.5-8) 4.4.5".section.note.GNU-stack,"",@progbits




非常遗憾,对于这个逻辑可能是因为调用外部的库或者受这个程序外部因素影响太大,无法再进行优化了。

本次测试用的GCC版本为4.4.5,操作系统为GNU/Linux,优化选项均为-O3。

上面说到的一般情况我是这么理解的,比如a == a并不一定是一个原子操作,就这么简单一个逻辑表达式可能经历的操作是先把a的值从内存放入寄存器1,再把b的值放入寄存器2,再进行比较,其间寄存器和内存的值都有可能被别的进程或线程打断并设法修改。不过很明显,如果连a == a都无法确定,那么编译器的优化手段几乎无从下手了,因为你什么都无法确定。

原创粉丝点击