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.

还有一些就是数值计算的优化了,不过这里就不介绍了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值