|
编译器优化的局限性 没有万能的东西,编译器也一样。现代编译器都会对源代码进行优化,以提高程序的性能。比如linux下的GCC编译器就能控制优化的等级,优化等级高,对应的程序性能好。对于给定的代码,编译器并不能保证能得到最好的性能,它也有局限性。所以才需要程序员能写出编译器易于理解和优化的代码。
编译器简单的说是讲程序语言代码翻译为机器码,既然是翻译,就不能改变程序想要表达的意思。所以编译器对程序总是小心的使用安全的优化。也就是说:优化后的版本和未优化的版本有一样的行为。
下面简单说一下编译器的局限性
1、存储器别名引用
存储器别名引用就是两个不同的指针可能指向存储器中的同一个位置。
例子说明:看如下的代码
两个程序似乎有相同的行为。都是将存储在*yp处的值两次加到*xp处存储的位置。这时,twiddle2的效率会更高一些。(可以认为第二个是第一个的简单优化)
第一个函数需要6次存储器引用(读*xp两次,写*xp两次,读*yp两次)而第二个函数只需3次存储器引用(读*yp两次,写*xp一次)
当然上面讨论的是在*xp和*yp指向不同位置的基础上。
现在考虑*xp和*yp指向同一位置的情况:
函数twiddle1的结果是*xp的值翻四倍,twiddle2得到的是3倍。编译器并不知道twiddle1会如何被引用,即不知道*xp,*yp是否指向存储器的同一位置,如果指向同一位置,编译器就不能把函数1优化为函数2的形式。因为他们有不同的行为。
为了编译后程序行为不被改变,也就是所谓的安全优化,编译器只能假设*xp 、*yp 会指向相同的位置。也就是说编译器不会把函数1优化为函数2的形式。这造成了一个妨碍优化的因素。
2、函数副作用
用例子说明问题,看下面简单的代码:
简单的看两个函数能产生相同的结果。同样的,可以暂时认为func2是func1的优化版本。
但是func2 调用了f()一次,而func1调用了f()两次。如果他们调用的函数f()修改了全局变量,结果就会有所不同
考虑如下f()代码:
int f()
{
return counter++;
}
这个函数修改了全局变量counter,函数调用的次数会改变程序的行为。也就是说:这个函数有副作用。
大多数编译器在代码优化的时候不会试图判断一个函数是否有副作用。为了安全的优化,编译器会认为所有函数都有副作用。
这也成为妨碍编译器优化的另一因素。
对于函数会有副作用的情况,在写代码的时候就要“帮助”编译器做出判断。和上面的代码对应的,如果函数f()没有副作用,在写程序的时候就把代码写成func2的形式。因为编译器是不会把func1()优化为func2()形式的。
|