`
simohayha
  • 浏览: 1386500 次
  • 性别: Icon_minigender_1
  • 来自: 火星
社区版块
存档分类
最新评论

gcc的几个自动优化

阅读更多
我的gcc版本是4.4.1

先来看const和define以及enum定义,编译器会做什么样的优化:

enum { constant=23 };
#define CONSTANT 23
static  const int Static_Constant=23;
const int Constant = 23;

int foo() {
    a(constant+3);
    a(CONSTANT+4);
    a(Static_Constant+5);
    return Static_Constant + Constant;
}


ok,我们然后来看对应的汇编代码(我这里用O2编译):

foo:
	pushl	%ebp
	movl	%esp, %ebp
	subl	$24, %esp
//constant+3=26
	movl	$26, (%esp)
	call	a
///CONSTANT+4=27
	movl	$27, (%esp)
	call	a
///Static_Constant+5=28
	movl	$28, (%esp)
	call	a
///Static_Constant + Constant = 23 + 23 = 46
	movl	$46, %eax
	leave
	ret
	.size	foo, .-foo
.......................................

///被保留。
Constant:
	.long	23
	.ident	"GCC: (Ubuntu 4.4.1-4ubuntu8) 4.4.1"


1 可以看到编译器会将它们的值直接计算然后再调用函数a,也就是+那一步被编译器优化掉了。

2 没有static修饰的const变量的那块内存被保留了,尽管foo并没有引用这块内存。

接下来看宏和内联,编译器会做什么不同的优化:

先看测试代码:

#define abs(x) ((x)>0?(x):-(x))

static long abs2(long x) { 
    return x >= 0 ? x : -x; 
}  

long foo2(long a) {
    return abs(a);
}


long bar(long a) { 
    return abs2(a);
}


然后来看汇编代码。这里只是汇编代码·片断:

foo2:
	pushl	%ebp
	movl	%esp, %ebp
	movl	8(%ebp), %eax
	popl	%ebp
	movl	%eax, %edx
	sarl	$31, %edx
	xorl	%edx, %eax
	subl	%edx, %eax
	ret
	.size	foo2, .-foo2
	.p2align 4,,15
.globl bar
	.type	bar, @function
bar:
	pushl	%ebp
	movl	%esp, %ebp
	movl	8(%ebp), %eax
	popl	%ebp
	movl	%eax, %edx
	sarl	$31, %edx
	xorl	%edx, %eax
	subl	%edx, %eax
	ret
	.size	bar, .-bar
	.p2align 4,,15


bar和foo2的代码完全一样,由此可见bar函数自动被内联了。

下面这个测试我用的gcc是4.3

现在我们来去掉static,然后来看会出现什么情况,源码:

long abs2(long x) { 
    return x >= 0 ? x : -x; 
}  

long foo2(long a) {
    return abs(a);
}


long bar(long a) { 
    return abs2(a);
}


来看汇编:


abs2:
	pushl	%ebp
	movl	%esp, %ebp
	movl	8(%ebp), %eax
	popl	%ebp
	movl	%eax, %edx
	sarl	$31, %edx
	xorl	%edx, %eax
	subl	%edx, %eax
	ret
bar:
	pushl	%ebp
	movl	%esp, %ebp
	movl	8(%ebp), %eax
	popl	%ebp
	movl	%eax, %edx
	sarl	$31, %edx
	xorl	%edx, %eax
	subl	%edx, %eax
	ret


可以看到依然会被内联,可是和没有加static相比,会生成abs2这个段.这是因为编译器要做最保守的处理,如果不加static的话,abs2可能还被其他的文件所调用,因此会保留这个abs2.

这里来做个总结吧.实验代码就不贴了,有兴趣可以自己去试试.

1 用O3编译,函数加不加static ,都会被内联.


2 如果函数被调用一次,并且函数体很大

如果为static修饰的函数,他无论如何都会被内联.

如果不加static的话,就需要你强制O3优化了,用O3也会生成abs2那个段.如果O2编译,则会生成跳转指令.

3 如果函数被调用多次.

加不加static修饰的函数,调用次数超过一定的数值都会生成跳转指令.


因此我们一个文件内的内部函数尽量都声明为static的。

接下来来看数组的边界检测,编译器会如何优化:

static char array[100000];

static int write_to(int ofs,char val) {
///检测边界(这个会被优化掉)
    if (ofs>=0 && ofs<100000)
        array[ofs]=val;
}

int main() {
    int i;
    for (i=0; i<100000; ++i) array[i]=0;//<这里没有检测边界
    for (i=0; i<100000; ++i) write_to(i,-1);
}


然后来看对应的汇编代码,其中L6表示第一个赋值,L7为第二个赋值:


.L6:
	movl	$0, array(%eax)
	addl	$4, %eax
	cmpl	$100000, %eax
	jne	.L6
	xorl	%eax, %eax
	.p2align 4,,7
	.p2align 3
.L7:
	movl	$-1, array(%eax)
	addl	$4, %eax
	cmpl	$100000, %eax
///可以看到边界检测被优化掉了
	jne	.L7
	popl	%ebp
	ret
	.size	main, .-main
	.p2align 4,,15


可以看到生成了相同的代码,边界检测被编译器优化了(也就是删除掉了).也就是循环的时候(如果我们for循环的边界检测刚好包含本身的边界检测的话,我们不需要多余的边界检测.)如果将for循环的边界检测改为大于100000的话,我们就会看到,编译器会生成相应的边界检测的.


static char array[100000];
 static int write_to(int ofs,char val){
          if(ofs>=0&&ofs<99999)
                  array[ofs]=val;
  }
  int main(){
         int i;
          for(i=0;i<99998;++i)array[i]=0;
          for(i=1;i<100000;++i)write_to(i,-1);
 }


汇编代码(只看write_to那部分):


.L6:
	movb	$-1, array(%eax)
	addl	$1, %eax
	cmpl	$100000, %eax
	je	.L4
	cmpl	$99999, %eax
	jne	.L6
.L4:
	popl	%ecx
	popl	%ebp
	leal	-4(%ecx), %esp


可以看到当for的边界检测包含write_to的边界的时候就会生成这段.

再来看另外的一些边界检测优化:


int regular(int i) {
///这里判断i的边界。
    if (i>5 && i<100)
        return 1;
    exit(0);
}


来看regular的汇编代码:

regular:
	pushl	%ebp
	movl	%esp, %ebp
	subl	$24, %esp
	movl	8(%ebp), %eax
///先将i减去6
	subl	$6, %eax
///然后和93比较
	cmpl	$93, %eax
	ja	.L18
	movl	$1, %eax
	leave
	ret
.L18:
///设置返回值为0
	movl	$0, (%esp)
	call	exit


可以看到i>5 && i<100被优化为 i-6 <93。并且编译器知道exit不会有返回值,因此还会设置正确的返回值。

接下来来看for和while,编译器会有什么不一样的处理:


char array[100000];
int foo5(int a)
{
    int i;
    for (i=1; i<a; i++)
        array[i]=array[i-1]+1; 

}

void foo6(int a)
{
    int i =1;
    while (i<a) {
        array[i]=array[i-1]+1;
        i++;
    }
}


来看生成的汇编:

foo5:
	pushl	%ebp
	movl	%esp, %ebp
	movl	8(%ebp), %ecx
	pushl	%ebx
	cmpl	$1, %ecx
	jle	.L23
	movzbl	array, %ebx
	movl	$1, %eax
foo6:
	pushl	%ebp
	movl	%esp, %ebp
	movl	8(%ebp), %ecx
	pushl	%ebx
	cmpl	$1, %ecx
	jle	.L29
	movzbl	array, %ebx
	movl	$1, %eax


可以看到完全一模一样,我记得在老的版本的gcc中,for和while生成的汇编是不一样的。

还有gcc的switch优化为跳转表可以看我前面的blog.

还有一些就是数值计算的优化了,不过这里就不介绍了。








分享到:
评论
2 楼 lurker0 2010-02-13  
》》可以看到i>5 && i<100被优化为 i-6 <93。

如果i为99  左侧条件为真,右侧条件为假,就不等价。

这里是不是有问题?
1 楼 seen 2010-02-03  
>>可以看到i>5 && i<100被优化为 i-6 <93

从数学上来说是说不通的。
这里有点意思。。。以前不知道还有这一手。。。在CF上玩了点小花样

相关推荐

    闻名企业网站系统 5.3 繁体中文UTF8.rar

    其强大的功能体现在以下几个方面: 2010-10-26 更新 1.程序代码简洁严谨, 整个系统程序仅2M左右大小. 2.中英文双语版共用一套网站程序, 双语页面实现自由切换. 3.GB2312和UTF-8双编码格式, 方便用户使用不同编码...

    编译原理(第2版)课件

    13.5.4 修改GCC源程序的窥孔优化 练习 第14章 面向对象语言的编译 14.1 面向对象语言的基本概念 14.2 面向对象语言语法结构及语义处理的特征 14.2.1 面向对象语言的类的语法结构及语义 14.2.2 面向对象语言的有效类...

    llvm编译器框架下clang编译器-易语言

    LLVM是Apple官方支持的编译器,而该编译器的前端是Clang,这...我没有上传clang.dll,这个几十M大,自己安装个llvm,到安装目录下 D:\Program Files\LLVM\bin\libclang.dll (这个是我的路径)复制到源码下就可以玩了。

    Dev:BinTuner

    由于实验持续了几个月,包括使用多个编译器进行的广泛测试,我们获得了十万个二进制中间文件,我们可以想象文件的大小,因此我们试图寻找其他方式来上传和共享中间文件文件和实验结果。 BinTuner的体系结构: ...

    iOS ARC 完全指南

    1. Xcode带了一个自动转换工具,可以迁移源代码至ARC 你可以手动转换源文件 3.你可以在 Xcode中禁用某些文件使用ARC,这点对于第三方库非常有用。 Xcode的自动迁移工具 ARC是LLⅧM3.0编译器的特性,而现有工程可能使用...

    你必须知道的495个C语言问题

    我在设计一个状态机,用函数表示每种状态,每个函数都会返回一个指向下一个状态的函数的指针。可我找不到任何方法来声明这样的函数——感觉我需要一个返回指针的函数,返回的指针指向的又是返回指针的函数……,如此...

    C语言FAQ 常见问题列表

    o 3.7 是否有自动比较结构的方法? o 3.8 如何向接受结构参数的函数传入常数值? o 3.9 怎样从/向数据文件读/写结构? o 3.10 我的编译器在结构中留下了空洞, 这导致空间浪费而且无法与外部数据文件进行 "二进制...

    《你必须知道的495个C语言问题》

    2.22 有没有一种自动方法来跟踪联合的哪个域在使用? 30 枚举 31 2.23 枚举和一组预处理的#define有什么不同? 31 2.24 枚举可移植吗? 31 2.25 有什么显示枚举值符号的容易方法吗? 31 位域 31 2.26 ...

    你必须知道的495个C语言问题(PDF)

    2.7 是否有自动比较结构的方法? . . . . . . . . . . . . . . . . . . . . 8 2.8 如何向接受结构参数的函数传入常数值? . . . . . . . . . . . . . . 8 2.9 怎样从/向数据文件读/写结构? . . . . . . . . . . . ....

    操作系统(内存管理)

    然后,当通过 free() 将该指针传递回来时,我们只需要倒退几个内存字节就可以再次找到这个结构。 在讨论分配内存之前,我们将先讨论释放,因为它更简单。为了释放内存,我们必须要做的惟一一件事情就是,获得我们...

Global site tag (gtag.js) - Google Analytics