c语言编译器优化,浅谈编译器优化

回顾

在“图灵社区:检测 2 的幂”中,我给出了以下函数:

int isPowerOf2a(long x)

{

if (x <= 0) return 0;

while (x % 2 == 0) x /= 2;

return x == 1;

}

在二进制计算机上,以上对 2 取模和除以 2 的运算可以使用按位与和向右移位运算代替:

while ((x & 1) == 0) x >>= 1;

在 Arch Linux 64-bit 操作系统中使用 clang 编译器进行优化编译后,得到以下结果:

$ clang -O isPowerOf2.c && ./a.out

isPowerOf2a: 49.7 seconds, 34

isPowerOf2b: 41.5 seconds, 34

isPowerOf2c: 29.7 seconds, 34

isPowerOf2d: 32.7 seconds, 34

反汇编

使用 objdump -d a.out 进行反汇编,经整理后得到:

d6f6aea2ff9ed9b00413c8ca80b46eb1.png

可以看出,在 isPowerOf2a 函数中,clang 编译器优化x % 2为x & 1,避免了除法运算。但是,x /= 2被优化为以下 4 条指令(在此之前,已经执行了z = x):

z >>>= 63; // z = (x < 0) ? 1 : 0;

z += x;     // z = (x < 0) ? (x+1) : x;

z >>= 1;   // z /= 2;

x = z;

这是为了处理负整数的情形。负整数用二进制补码表示时,算术右移 1 位的效果,相当于除以 2 时向负无穷大截断。而在主流的 C 编译器中,整数除法是向 0 截断的。

如果能够保证x是非负整数,x /= 2可以直接优化为x >>= 1。实际上,因为前面有:

if (x <= 0) return 0;

执行到这里时,x必定是非负整数,但是 clang 编译器的优化功能还没有达到这个水平。

使用无符号整数

把 isPowerOf2.c 中的所有 long 修改为 unsinged long 后,再次编译和运行,得到:

$ clang -O isPowerOf2u.c && ./a.out

isPowerOf2a: 38.1 seconds, 34

isPowerOf2b: 38.9 seconds, 34

isPowerOf2c: 25.0 seconds, 34

isPowerOf2d: 32.9 seconds, 34

使用 objdump -d a.out 进行反汇编,整理后得到:

0:

1:

2:

3:

4:

5:

6:

7:

8:

9:

10:

11:

0000000000400520 :

400520: xor %eax,%eax

400522: test %rdi,%rdi

400525: je 400544

400530: mov %rdi,%rax

400533: shr %rdi

400536: test $0x1,%al

400538: je 400530

40053a: cmp $0x1,%rax

40053e: sete %al

400541: movzbl %al,%eax

400544: retq

0000000000400550 :

400550: xor %eax,%eax

400552: test %rdi,%rdi

400555: je 400574

400560: mov %rdi,%rax

400563: shr %rdi

400566: test $0x1,%al

400568: je 400560

40056a: cmp $0x1,%rax

40056e: sete %al

400571: movzbl %al,%eax

400574: retq

可见,对于非负整数,clang 编译器能够很好地优化x /= 2为x >>= 1。与上一小节的使用有符号整数的 isPowerOf2b 函数对比:

第 3 行:jle指令改为je指令,即x <= 0改为x == 0。

第 5 行:sar指令改为shr指令,即算术右移改为逻辑右移。

奇怪的是,isPowerOf2a 居然比 isPowerOf2b 更快,这没道理啊,两者的汇编语言代码是一样的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值