比如前面各种关于循环的优化中,我们如果要计算依赖向量,起码编译器要能够识别循环变量,
而识别循环变量也是编译器的一个优化过程。
而通常编译器所认为的循环变量可能同我们所看到的有所不同。
通常的循环变量是狭义的,也就是说,如果这个变量在一个循环的任意两次相邻的迭代中变换值是常数,
那么这个变量就是循环变量。最常见的循环变量是如下代码中:
- for(i=0;i<n;i++){
- ....
- }
但是如果循环体中对i做了修改,那么虽然看上去i还像是一个循环变量,但是对于编译器来说,i已经不是循环变量了,如:
- for(i=0;i<n;i++){
- if(..){
- i++;
- }
- ...
- }
而还有一些情况,循环变量人工不容易看出来,但是编译器确可以判断出来,如:
- i=0;
- while(...){
- j=i+1;
- ...
- k=j+2;
- ...
- i=k;
- ...
- }
其中每次迭代这三个变量都增加了3.
而对于编译器来说,通常还可以识别一些更加复杂的循环变量,如:
- i=0;
- while(...){
- j=i+1;
- ...
- k=j+2;
- h=a*k+j;
- ...
- i=k;
- u=h-i+b;
- ...
- }
所以编译器可以识别出它们也是循环变量。(其中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=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]=...;
- ...
- }
做一些前面提到过的如么模变换,仿射变换之类的优化。反而对于后面的代码,由于通常编译器是不会将移位运算转化为乘法运算的,所以
通常的编译器反而无法知道后面的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=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;
- }
同样道理,对于三个循环变量的相乘,从理论上,我们同样可以转化为若干次加法运算。
不过据我所知,并没有编译器真正这样去做,毕竟实际中,这样代码的例子会非常少见。
当然,如果换成用郭的HugeCalc代码中大整数做循环变量的代码,那么遇到上面的代码编译器
的优化同样无能为力了,那么就需要手工做类似的优化了。