从一个计算 数组累加和 的小程序看 程序性能优化 的小技巧

  • Post author:
  • Post category:小程序


最近在图书馆随便翻翻的的时候,看到一本因特尔Intel出的《多核多线程技术》,冲着名字,也得拿起来看看啊。翻过之后,内容泛泛,感觉是一门入门读物。但就是这本薄薄的一本小书,定价愣是为49.5元,可能就是因为被惯了intel的名字吧。如果希望看,还是到读书馆去看吧。

在里面看到了一个小例子,说的是有关“

性能调优方法

”的,还是有点意思的。

数组累加和问题描述


已知:


有一个结构体,保存两个变量:一个是指向整型数组的指针,一个该数组的长度


问题:


求这个数组中所有元素的累加和,就是所有项的和


数据定义和相关方法:


如下所示

#include <stdio.h>
#include <stdlib.h>

typedef struct {
    int len;
    int *data;
} vec_rec, *vec_ptr;

//分配内存,参数len表示结构体中的数组长度
vec_ptr new_vec( int len ) {
    //动态分配结构体内存
    vec_ptr result = (vec_ptr)malloc( sizeof(vec_rec) );
    if(!result) {
        return NULL;
    }
    if(len>0) {
        //动态分配数组内存
        int *data = (int *) calloc(len, sizeof(int));
        if(!data) {
            free( result );
            return NULL;
        }
        result->len = len;
        result->data = data;
    } else { //len小于0
        result->data = NULL;
        result->len = 0;
    }
    return result;
}

//获得数组指定下标的元素,在参数dest中返回,函数的结果表示是否成功获得元素值
int get_vec_element( vec_ptr v, int index, int *dest ){
    if( index < 0 || index >= v->len ){
        return 0;
    }
    *dest = v->data[index];
    return 1;
}

//获得结构体中的数组长度
int vec_length(vec_ptr v){
    return v->len;
}

//获得数组的起始位置
int * get_vec_start(vec_ptr v){
    return v->data;
}

初步解答代码如下:

初步解法

程序很简单,写出的计算代码和测试代码如下所示:

//第一次实现
void combine1(vec_ptr v, int* dest){
    int i;
    *dest = 0;
    for(i=0; i<vec_length(v); i++){
        int val;
        get_vec_element(v, i, &val);
        *dest = *dest + val;
    }
}

int main(int argc, char **argv) {
    vec_ptr p = new_vec(5);
    //给结构体中的数组赋初值
    int i;
    for(i=0; i<5; i++){
        p->data[i] = i+1;
    }
    //计算累加和
    int result=0;
    combine1(p, &result);
    printf("result:  %d /n", result);//打印结果为15
    return 0;
}

优化一:消除循环不变量

在combine方法的for循环中,验证条件 i<vec_length(v) 每次都要调用vec_length()函数,而实际上每次返回的值都是一样的。


如果验证条件里有函数调用,编译器是没有办法进行优化的。

既然vec_length的返回值是不变的,将其提取出来,代码可以修改成下面的形式:

//优化一:消除循环不变量
void combine2(vec_ptr v, int* dest){
    int i;
    *dest = 0;
    int length = vec_length(v); //提取vec_length(v)
    for(i=0; i<length; i++){
        int val;
        get_vec_element(v, i, &val);
        *dest = *dest + val;
    }
}

优化二:减少不必要的函数调用

在combine方法中,每次都要调用get_vec_element()函数,在该函数内部首先判断下标是否合法。这里包含

两个

开销:一个是函数调用(涉及到上下会转换,指令跳转等);一个是函数内部的条件判断;条件判断检查牵扯到条件转移指令,进而牵扯到操作系统的流水线机制中“猜测式执行”,可能会造成指令流水线停顿或回溯。

可以利用数组元素每个元素都

连续存放

的特点,对程序进行下面优化:

//优化二:消除不必要的函数调用
void combine3(vec_ptr v, int* dest){
    int i;
    *dest = 0;
    int length = vec_length(v); 
    int * data = get_vec_start(v);//获取开始位置
    for(i=0; i<length; i++){
        *dest += data[i];//利用数组元素的连续存放性
    }
}

优化三:消除不必要的内存读取

在combine方法中,循环体内每次都要对*dest执行读取操作,由于  *dest += data[i]  相当于 *dest += *dest +data[i] ,所以每次循环要进行2次内存读、1次内存写,代价比较昂贵。

如果你知道,

函数中的 局部变量 一般会放到寄存器中

,你就可以进行下面优化:

//优化三:消除不必要的内存读取
void combine4(vec_ptr v, int* dest){
    int i;
    //*dest = 0;  //该行可以去掉
    int result = 0; //利用局部变量保存结果
    
    int length = vec_length(v); 
    int * data = get_vec_start(v);
    for(i=0; i<length; i++){
        result += data[i];
    }
    *dest = result; //最后将结果赋值给 *dest
}

总结

在你确定了程序使用的

数据结构、算法、

以及

整体结构

之后,还有几个小技巧可以继续优化你的程序:

1. 避免多余的函数调用;

2. 避免多余的条件判断和边界检查;

3. 利用局部变量保存中间计算结果;


本文链接:


http://blog.csdn.net/daheiantian/archive/2011/02/23/6452199.aspx



版权声明:本文为daheiantian原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。