优化代码建议

程序性能提高技术

  1. 高级设计:为遇到的问题选择适当的算法和数据结构。要特别警觉,避免使用那些会渐进产生糟糕性能的算法或者编码技术。当然,选择合适的算法和数据结构需要你掌握常见的算法和数据结构的前提。
  2. 基本的编码规则。
    (1):消除连续的函数调用:这种优化也称为代码移动,识别要执行多次但是计算结果不会改变的计算。
    //这里使用c++语言进行示例:
/**
 * Convert string to lowercase: slow version
 */
void lower1(char* s){
    for(int i=0;i<myStrlen(s);i++){ //低效率的地点
        if(s[i]>='A'&&s[i]<='Z'){
            s[i]-=('A'-'a');
        }
    }
}


/**
 * Convert string to lowercase: faster version
 */
void lower2(char* s){

    int len=myStrlen(s);//优化地方
    for(int i=0;i<len;i++){
        if(s[i]>='A'&&s[i]<='Z'){
            s[i]-=('A'-'a');
        }
    }
}


/**
 * Sample implementation of library function strlen
 * Compute length of string
 */
int myStrlen(const char* s){
    int length=0
    while(*s!='\0'){
        s++;
        length++;
    }
    return length;
}

//在这里,可能有很多人为了图方便,就在循环里面进行调用函数,然而就是这样方便造成了程序的低效率。所以,在写程序时,要注意循环的低效率,尽可能把计算移到循环体外,不要在循环体内进行过多的运算。将函数调用的结果保存到临时变量中

(2):消除不必要的存储器引用
//引用临时变量来保存中间结果。只有在最后的值计算出来时,才将结果存放到数组或者全局变量中。

/**
 * sumByVector : slow version
 */
void sumByVector(int* v,int* result){

    int length=getVecLength(v);//获取数组的长度
    int* data=getVecStart(v); //获取数组的首地址
    *dest=0;
    for(int i=0;i<length;i++){
        *dest += data[i];
    }  
}

/**
 1. sumByVector : faster version
 */
void sumByVector(int* v,int* result){

    int length=getVecLength(v);//获取数组的长度
    int* data=getVecStart(v); //获取数组的首地址
    int acc = 0;
    for(int i=0;i<length;i++){
        acc += data[i];
    }  
    *result=acc;
}//使用临时变量来保存中间结果,消除了每次循环迭代中从存储器读出并将更新回写
//的需要

//在这里需要注意的是,应该使用使用临时变量来保存中间结果

3:低级优化
(1):展开循环,降低开销,建议不要展开太多
//循环展开:是一种程序变换,通过增加每次迭代计算的元素的数量,减少循环的迭代次数。

/**
 * sumByVector : faster version 
 * 展开循环 loop by 2
 */
void sumByVector(int* v,int* result){

    int length=getVecLength(v);//获取数组的长度
    int* data=getVecStart(v); //获取数组的首地址
    int acc = 0;
    for(int i=0;i<length;i+=2){
        acc += (data[i]+data[i+1]);
    }  
    *result=acc;
}//使用临时变量来保存中间结果,消除了每次循环迭代中从存储器读出并将更新回写
//的需要

(2):用功能的风格重写条件操作,使得编译采用条件数据传送。

/**
 * Rearrange two vectors so that for each i,b[i]>=a[i]
 */
void minmax2(int a[],int b[],int n){
    int i;
    for(i=0;i<n;i++){
        int min=a[i]<b[i]?a[i]:b[i];//条件数据传送
        int max=a[i]<b[i]?b[i]:a[i];
        a[i]=min;
        b[i]=max;
    }
}

//尽量使用?:运算符来代替条件分支

(3):通过使用例如多个累积变量和重新结合等技术,找到方法提高指令级并行。

4: 矩阵运算的优化

(1):编写高速缓存友好代码方法:
1):让最常见的情况运行的更快。程序通常把大部分时间都花在少量的核心函数上,要把注意力集中在核心函数的循环上,而忽略其他部分
2):在每个循环内部缓存不命中数量最小。在其他条件(例如加载和存储的总次数)相同的情况下,不命中率低的循环运行的更快。

int sumVec(int v[N])
{
    int i,sum=0;
    for(i=0;i<N;i++){
        sum+=v[i];
    }
    return sum;
}

//上面代码,说明编写高速缓冲友好的代码的重要问题:

  • 对局部变量的反复引用是好的,因为编译器能够将它们缓存在寄存器文件中(时间局部性)

  • 步长为1的引用模式是好的,因为存储器层次结构中所有层次的缓存都是讲数据存储为连续的块(空间局部性),对于多维数组进行操作的程序,空间局部性尤其重要。应该尽量总是以行优先为主(原因存储器层次结构中所有层次的缓存都是讲数据存储为连续的块(空间局部性),一次也只能加载一部分连续的块进高速缓存。)

//反例
int sumarraycols(int a[M][N]){

    int i,j,sum=0;
    for(j=0;j<N;j++){
        for(i=0;i<M;i++){
            sum+=a[i][j];
        }
    }
    return sum;
}

//正例:
int sumarraycols(int a[M][N]){

    int i,j,sum=0;
    for(i=0;i<M;i++){
        for(j=0;j<N;j++){
            sum+=a[i][j];
        }
    }
    return sum;
}

//从上述程序片段反例可以看出:此时得到步长为N的引用模式,很大程度上降低程序的执行效率。在数组的访问时,尽量使用步长不是很大的引用模式,因为步长越小,在访问存储器时不会跳来跳去,造成程序效率执行很差。

5:一些优化的忠告:
要警惕,在为了提高效率重写程序时避免引入错误。在引入新变量,改变循环条件和使得代码整体上更复杂时,很容易放错误。一项有用的技术是在优化函数时,用检查代码来测试函数的每个版本,以确保这个过程没有引入错误。检查代码对函数新版本实施一系列的测试,确保它们产生与原来一样结果。对于高度优化的代码,这组测试情况必须变得更加广泛,因为要考虑的情况很多。例如,使用循环展开的检查代码需要测试很多不同的循环界限,保证它能够处理最终单步迭代所需要的所有不同的数字。

6:最后介绍一下Amdahl定律
(1):主要思想:当我们加快系统一部分速度时,对系统整体性能的影响依赖于这个部分有多重要和速度提高了多少。(要想大幅度提高整个系统的速度,必须提高系统很大部分的速度)

猜你喜欢

转载自blog.csdn.net/a169388842/article/details/78517649