循环不变量的优化

转自: http://blog.csdn.net/mathe/article/details/1175620

在九十年代末时,我一个同学在写一个处理医学图像的程序,里面用了不少三角函数 ,所以程序运行很慢(那时机器也慢,可能主频都在100M左右吧),处理一个图片都要20多秒。然后他向我询问,有没有什么办法可以提高运行速度。
我看了一下他的代码,做了下简单的修改,速度一下子就提高了3倍多。原因在于,他的代码里面有一些循环不变量,可以做很简单的优化,比如他的代码如下:
for(x=0;x   for(y=0;y       for(z=0;z            sum+= sin(x)*A[x][y][z]+sin(y)*B[x][y][z]+sin(z)*C[x][y][z];
      }
  }
}

象这样的代码,每次循环z进来以后,其实x,y都不会发生变化,所以没有必要重新计算sin(x),sin(y).可以由于sin(x),sin(y)是函数调用,编译器不一定能够知道这里函数调用不需要重复产生。所以如果完全让编译器去做,它就不一定能够消除这些重复的函数调用,那么运行速度自然就会比较慢,而如果我们把上面代码改成:
for(x=0;x   double sinx=sin(x);
   for(y=0;y       double siny=sin(y);
       for(z=0;z            sum+= sinx*A[x][y][z]+siny*B[x][y][z]+sin(z)*C[x][y][z];
      }
  }
}

那么我们就可以通过手工的方法,将循环不变量(关于循环z)提升到循环z的外面,从而减少了对这种函数调用的访问,从而提高了速度。
当然这种优化,现在有些编译器已经能够对部分表达式做到,比如象上面的sin(.)函数,编译器可以事先识别一些常数的库函数,比如三角函数等,它知道这些函数不会有副作用,所以对这些函数,使用相同参数的重复调用就可以消除了。但是对于更多的情况,编译器还是无法分析,这就需要我们在写程序时,多加注意,从而能够写出质量更高的代码。
比如对于下面的函数:
int sqr_sum(double *err, double a[], int size_a, double b[], int size_b){
   int i;

   if(err==NULL||size_a!=size_b)
        return 0;

  *err=0;
  for(i=0;i       *err+=(a[i]-b[i])*(a[i]-b[i]);
  return 1;
}
这是一个非常常见的代码,但是它的效率就不够高,最主要的原因是循环里面要反复访问内存*err.
这个循环内部的代码展开后实际类似:
   load *err;
   load a[i];
   load b[i];
   计算 ...
   store *err;
由于err是个指向double类型的指针,编译器无法判断err是否会指向数组a[.],b[.],所以上面的四个内存访问都有可能访问到同一个内存地址,这这种情况下,编译器就无法交换它们读写内存的顺序,从而,无法做进一步的优化。
但是如果我们将代码改写为:
int sqr_sum(double *err, double a[], int size_a, double b[], int size_b){
   int i;
   double local_err;

   if(err==NULL||size_a!=size_b)
        return 0;

  local_err=0;
  for(i=0;i       local_err+=(a[i]-b[i])*(a[i]-b[i]);
  *err = local_err;
  return 1;
}
那么,这个代码的性能将会高很多。首先,编译器可以将局部变量local_err放在寄存器中,从而所有对local_err的访问都不需要经过内存,从而减少了内存访问的次数,这提高了访问速度,而且减少了指令数目。
其次,由于编译器知道local_err同数组a[],b[]等的内存都不重叠,从而这个循环的每两次执行的语句访问的内存空间必然完全不同,我们完全可以让这些不同语句并行执行。那么在支持SSE的机器上,我们就可以让多条语句由一条SSE语句来并行执行。同时,对于多CPU的机器,我们可以让多个CPU来并行执行,比如第一个CPU累加前面的部分,第二个CPU累加后面部分,完成以后,在统一累加一次就可以了。

更多关于编译器优化的介绍请看:
http://bbs.emath.ac.cn/thread-173-1-1.html

你可能感兴趣的:(循环不变量的优化)