• 循环不变量的优化


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

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

  • 相关阅读:
    Swift中枚举的总结以及使用
    CapsLock Enhancement via AutoHotKey
    计算思维
    计算几何-凸包算法 Python实现与Matlab动画演示
    CapsLock魔改大法——变废为宝实现高效编辑
    Python调用Matlab2014b引擎
    VC++如何利用Matlab2014b的图形引擎进行绘图
    Window中C++进行精确计时的方法
    十四。算法小知识点
    十三。宫水三叶公众号总结
  • 原文地址:https://www.cnblogs.com/CYP01/p/2742988.html
Copyright © 2020-2023  润新知