循环变量的优化
如今来介绍编译器如何识别循环变量的问题。
好比前面各类关于循环的优化中,咱们若是要计算依赖向量,起码编译器要可以识别循环变量,
而识别循环变量也是编译器的一个优化过程。
而一般编译器所认为的循环变量可能同咱们所看到的有所不一样。
一般的循环变量是狭义的,也就是说,若是这个变量在一个循环的任意两次相邻的迭代中变换值是常数,
那么这个变量就是循环变量。最多见的循环变量是以下代码中:
若是循环体中没有对变量i的修改,那么i就必然是一个循环变量。
可是若是循环体中对i作了修改,那么虽然看上去i还像是一个循环变量,可是对于编译器来讲,i已经不是循环变量了,如:
- for(i=0;i<n;i++){
- if(..){
- i++;
- }
- ...
- }
复制代码
像上面的循环体,有时候i增长1,有时候i增长2,那么i就不是循环变量了。
而还有一些状况,循环变量人工不容易看出来,可是编译器确能够判断出来,如:
- i=0;
- while(...){
- j=i+1;
- ...
- k=j+2;
- ...
- i=k;
- ...
- }
复制代码
像上面的代码,若是没有其余对j,k,i的修改,那么这里i,j,k实际上都是循环变量,
其中每次迭代这三个变量都增长了3.
而对于编译器来讲,一般还能够识别一些更加复杂的循环变量,如:
- i=0;
- while(...){
- j=i+1;
- ...
- k=j+2;
- h=a*k+j;
- ...
- i=k;
- u=h-i+b;
- ...
- }
复制代码
像上面代码中,编译器首先能够识别出i,j,k是循环变量,而后编译器发现h,u是循环变量的线性组合,
因此编译器能够识别出它们也是循环变量。(其中a,b能够是变量,只要在循环体内部没有被修改)
好比h每一个循环改变的量为3*(a+1),u每一个循环改变的量为3*a,编译器能够经过将上面代码改变为:
- i=0;
- h=3*a+1;
- u=3*a+1+b;
- step1=3*(a+1);
- step2=3*a;
- while(...){
- j=i+1;
- ...
- k=j+2;
- ///h=a*k+j;///删除原先这里对h的定义
- ...
- i=k;
- ///u=h-i+b;///删除这里对u的定义
- ...
- h+=step1;
- u+=step2;
- }
复制代码
在通过这个变换之后,在循环体内部, 全部关于循环变量的计算都可以不包含乘法运算,从而比原先代码应该能够快一些。
一样,若是在编译器优化比较后面的部分,一般,对于数组的访问都已经被展开,
如代码
- for(i=0;i<n;i++){
- a[i] =....;
- }
复制代码
可能被展开成:
- for(i=0;i<n;i++){
- p=a+i*S; ///这里S是常数,表明数组a中每一个元素在内存中占用的空间大小
- *p=...;
- }
复制代码
那么对于编译器来讲,指针p也是一个循环变量,因此代码能够被转化为
- p=a;
- for(i=0;i<n;i++){
- *p=...;
- p=p+S;
- }
复制代码
变化之后一样计算地址中的乘法运算被消除了。
我看到郭给出连接中一篇英文文章中介绍到对于数组,最好让每一个元素数据的大小是2的幂,这样,计算每一个元素的地址时候,
乘法就能够被移位替换掉,从而提升了速度。可是,若是那样的数组一般都是被经过循环变量访问的,咱们能够看出来,彻底没有
必要作那样的优化(实际上那样可能会消耗更多的内存空间,从而性能更加差).
此外,有一些比较优秀的程序员,他们知道计算机计算移位比乘法运算快,因此对于下面的代码
- for(i=0;i<n;i++){
- a[2*i]=...;
- ...
- }
复制代码
他们可能写成了
- for(i=0;i<n;i++){
- a[i<<1]=...;
- ...
- }
复制代码
其实,对于编译器来讲,反而前面的代码更加容易优化,由于编译器能够很是容易识别出2*i是一个循环变量,从而咱们能够计算依赖向量,
作一些前面提到过的如么模变换,仿射变换之类的优化。反而对于后面的代码,因为一般编译器是不会将移位运算转化为乘法运算的,因此
一般的编译器反而没法知道后面的i<<1也是一个循环变量,从而阻止了进一步优化的可能。
此外,部分编译器还会对一些循环变量之间的相乘作优化(好比Open64),好比代码:
- i=0;
- while(...){
- j=i+1;
- ...
- k=j+2;
- h=a*k+j;
- ...
- i=k;
- u=h-i+b;
- ...
- sum+=h*u;
- }
复制代码
在编译器分析出h和u都是循环变量之后,编译器就能够对h*u作进一步优化
咱们知道 h=h0+i*step1,u=u0+i*step2;
因此h*u=h0*u0+(h0*step2+u0*step1)*i+i*i*step1*step2
分别对于i和i+1计算上面的表达式并相减,咱们能够获得对于第i次迭代,h*u的变换值是
h0*step2+u0*step1+step1*step2+i*2*step1*step2;
因此咱们知道,上面代码因而能够优化成:
- i=0;
- h=3*a+1;
- u=3*a+1+b;
- step1=3*(a+1);
- step2=3*a;
- hu=h*u;
- ddhu=2*step1*step2;
- dhu=h0*step2+u0*step1+step1*step2+ddhu*i;
- while(...){
- j=i+1;
- ...
- k=j+2;
- ///h=a*k+j;///删除原先这里对h的定义
- ...
- i=k;
- ///u=h-i+b;///删除这里对u的定义
- ...
- h+=step1;
- u+=step2;
- sum+=hu;
- hu+=dhu;
- dhu+=ddhu;
- }
复制代码
从而计算循环体内计算h*u的乘法运算由两次加法运算代替,从而提升了速度。
一样道理,对于三个循环变量的相乘,从理论上,咱们一样能够转化为若干次加法运算。
不过据我所知,并无编译器真正这样去作,毕竟实际中,这样代码的例子会很是少见。
固然,若是换成用郭的HugeCalc代码中大整数作循环变量的代码,那么遇到上面的代码编译器
的优化一样无能为力了,那么就须要手工作相似的优化了。
欢迎关注本站公众号,获取更多信息