首页 > 代码库 > 是什么阻止了优化

是什么阻止了优化

编译器会帮我们做很多优化,使我们写的很不优雅的代码也可以和大牛们写的得到比较接近的效率。当然,这前提是你们用的算法是一样的。编译器绝不会优化算法,不会把你的冒泡排序改成快排。但是,常数级别的优化也是很重要的。

编译器能做的事情有很多,在编译原理中我们知道,它可以消除死代码,提取公共子表达式等代码中妨碍效率的地方。但是,优化往往受到限制,因为它受到的最基础的约束就是不能改变程序的行为。这意味着,当它犹豫是否进行某一步优化时,它必须保守。就是说,它可以做很多事,但是对于稍有风险的事,它就选择不做。那么,都有哪些事情让优化感到风险呢?

第一个是函数调用。举个例子:

技术分享

这是一个将字符串改成小写的程序,分析一下程序复杂度为O(n),这看上去是一定的,其实不然,它的实际复杂度为O(n2)。问题出在strlen(s)上,这个函数本身就是O(n)的,执行了n次,所以时间复杂度为O(n2),从编译原理的课上所学来看,编译器应该会发现这个问题,将strlen()函数提出来啊。其实不然,它会担心在你的循环中,你改变了字符串s。所以,它不敢帮你提出strlen()函数。如果你优化时,在不了解循环中的事情就擅自提出了这个函数,那么就会产生不好的事情。

 

第二个是内存别名。别名就是有两个指针指向了同一块内存。看一个例子:

技术分享

这两个程序,看上去做的是一件事情,而且显然左边的效率更高一些。优化时很可能就将右边的代码改成了左边的。但是,如果指针xp和yp指向了同一个int那么结果就大大不同了。如果*xp = 1;那么计算过后左边结果*xp=3,右边*xp=2。这就是内存别名。

在优化过程中必须考虑内存别名的存在,如图:

技术分享

如果a和b指向了同一个数组,那么结果就会大不一样。

在优化过程中,我们必须小心一些,不然就会改变程序的行为。如果我们确信不存在上述情况,而且对于优化产生的后果和风险都坦然接受或置之不理,那么编译器也会帮你做一些事情。G++编译时使用的-O参数就是用来选择优化等级的。等级越高,编译器越激进。带来的风险就越大。-O  主要进行跳转和延迟退栈两种优化;-O2 除了完成-O1的优化之外,还进行一些额外的调整工作,如指令调整等。-O3 则包括循环展开和其他一些与处理特性相关的优化工作。-O2进行了代码调整,就意味着它可以将我们上文中提到的那么strlen()的问题进行优化。当然,这也只是我的个人猜测。

是什么阻止了优化