GCC的优化效果测试

有这么个程序:
#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
	.type	main, @function
main:
	pushl	%ebp
	movl	%esp, %ebp
	andl	$-16, %esp
	subl	$32, %esp
	leal	28(%esp), %eax
	movl	%eax, 4(%esp)
	movl	$.LC0, (%esp)
	call	__isoc99_scanf
	movl	$.LC1, (%esp)
	call	puts
	xorl	%eax, %eax
	leave
	ret
	.size	main, .-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
	.type	inc, @function
inc:
	pushl	%ebp
	movl	%esp, %ebp
	movl	8(%ebp), %eax
	popl	%ebp
	addl	$1, %eax
	ret
	.size	inc, .-inc
	.section	.rodata.str1.1,"aMS",@progbits,1
.LC0:
	.string	"%d"
.LC1:
	.string	"good!:)"
	.text
	.p2align 4,,15
.globl main
	.type	main, @function
main:
	pushl	%ebp
	movl	%esp, %ebp
	andl	$-16, %esp
	subl	$32, %esp
	leal	28(%esp), %eax
	movl	%eax, 4(%esp)
	movl	$.LC0, (%esp)
	call	__isoc99_scanf
	movl	$.LC1, (%esp)
	call	puts
	xorl	%eax, %eax
	leave
	ret
	.size	main, .-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
	.type	inc, @function
inc:
	pushl	%ebp
	movl	%esp, %ebp
	movl	8(%ebp), %eax
	popl	%ebp
	addl	$1, %eax
	ret
	.size	inc, .-inc
	.section	.rodata.str1.1,"aMS",@progbits,1
.LC0:
	.string	"%d"
.LC1:
	.string	"good!:)"
.LC2:
	.string	"bad!:("
	.text
	.p2align 4,,15
.globl main
	.type	main, @function
main:
	pushl	%ebp
	movl	%esp, %ebp
	andl	$-16, %esp
	pushl	%ebx
	subl	$44, %esp
	leal	28(%esp), %eax
	movl	%eax, 4(%esp)
	movl	$.LC0, (%esp)
	call	__isoc99_scanf
	movl	28(%esp), %ebx
	movl	%ebx, (%esp)
	addl	$1, %ebx
	call	inc
	cmpl	%eax, %ebx
	je	.L8
	movl	$.LC2, (%esp)
	call	puts
	addl	$44, %esp
	xorl	%eax, %eax
	popl	%ebx
	movl	%ebp, %esp
	popl	%ebp
	ret
	.p2align 4,,7
	.p2align 3
.L8:
	movl	$.LC1, (%esp)
	call	puts
	addl	$44, %esp
	xorl	%eax, %eax
	popl	%ebx
	movl	%ebp, %esp
	popl	%ebp
	ret
	.size	main, .-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
	.type	main, @function
main:
	pushl	%ebp
	movl	%esp, %ebp
	andl	$-16, %esp
	subl	$32, %esp
	leal	28(%esp), %eax
	movl	%eax, (%esp)
	call	time
	movl	$3, (%esp)
	call	sleep
	leal	24(%esp), %eax
	movl	%eax, (%esp)
	call	time
	movl	28(%esp), %eax
	cmpl	24(%esp), %eax
	je	.L2
	movl	$.LC0, (%esp)
	call	puts
	xorl	%eax, %eax
	leave
	ret
	.p2align 4,,7
	.p2align 3
.L2:
	movl	$.LC1, (%esp)
	call	puts
	xorl	%eax, %eax
	leave
	ret
	.size	main, .-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都无法确定,那么编译器的优化手段几乎无从下手了,因为你什么都无法确定。

你可能感兴趣的:(优化,Debian,gcc,测试,编译器)