第5章 优化程序性能

第5章 优化程序性能

5.1优化编译器的能力和局限性

  在第三章中讨论过,用-og会用一组比较基本的优化,-O2优化级别已经成为了比较被接受的标准,但还是主要考虑-O1编译出的代码

  编译器必须很小心的对程序进行优化。

 

像是这个例子中,twiddle1与twiddle2看似有相同的行为,但是如果xp , yp 指向同一个内存地址,产生的结果就会不一样。这个被称为内存别名使用(memory aliasing)

 

 5.2表示程序性能

  我们引入度量标准每元素的周期数(Cycles Per Element ,CPE)作为一种表示程序性能和帮助我们优化它的一种方法.

有三个概念比较容易混淆:

1.指令周期:指一条指令从取指到执行完成的时间

2.CPU周期:指一条指令被分为不同阶段,完成每个阶段需要的时间

3.时钟周期:这是一个基本时间单位,可以理解为阀门开关的时间。

  处理器的活动顺序是由时钟控制的,时钟提供了某个频率的规律信号,通常用千兆赫兹来表示(GHZ),即十亿周期每秒来表示。用时钟周期来表示,度量值表示的是执行了多少条指令。

 

像是这两个函数,韩舒而运用了循环展开(loop unrolling)的技术,每次迭代计算两个元素。

 

 

5.3程序示例

 

//一个生成向量、访问向量元素以及确定向量长度的基本过程
typedef struct{
    long len;
    data_t *data;
}vec_rec , *vec_ptr;
typedef long data_t;
vec_ptr new_vec(long len){
    vec_ptr result = (vec_ptr) malloc(sizeof(vec_rec));
    data_t *data = NULL;
    if(!result){
        return NULL;
    }
    result->len = len;
    if(len > 0){
        data = (data_t *)calloc(len,sizeof(data_t));
        if(!data){
            free((void *) result);
            return NULL;
        }
    }
    result->data = data;
    return result;
}

int get_vec_element(vec_ptr v,long index , data_t *dest){
    if(index < 0 || index >= v->len){
        return 0;
    }
    *dest = v->data[index];
    return 1;
}
long vec_length(vec_ptr v){
    return v->len;
}

下面我们定义一个对向量进行操作的函数

//我们可以用不同定义来对向量元素进行求和或者求乘积
#define IDENT 0
#define OP +
Or
#define IDENT 1
#define OP *

void combine1(vec_ptr v , data_t *dest){
    *dest = IDENT;
    for(long i = 1 ; i < v->len ; i++){
        long val;
        get_vec_element(v,i,&val);
        *dest = *dest OP val;
    }    
}

我们如果用-o1优化,就会显著的提高程序性能

 

5.4 消除循环的低效率

  在combine1中我们调用函数vec_length作为for循环的测试条件,也就是说循环一次就要进行一次vec_length的计算,大大延缓了程序的进行。

所以我们通过代码移动(code motion)来进行优化

5.5 消除过程调用

在combine1中,每次获取元素都要进行边际检测。大大减缓了程序速度。因此我们在combine3中,直接先获得第一个数组的指针。后面通过数组引用的方法来获得索引数据。

 

 

5.6 消除不必要的内存引用

在之前的combine中我们每次进行循环计算就要对dest进行内存访问,这也是不必要的,因此我们创造一个temp变量来进行中间计算

 

5.7 理解现代处理器

5.7.1整体操作

  我们要进一步的理解现代处理器的设计。假想的处理器设计是不太严格地基于近期的intel处理器结构。这些处理器成为超标量,意思是他可以在每个时钟周期执行多个操作,而且是乱序的。整个设计有两个主要部分:指令控制单元和执行单元。

 

 ICU从指令高速缓存中读取指令。当程序遇到分支的时候,它会采用一种分支预测的技术,处理器会猜测是否会选择分支,使用投机执行的技术开始取出预测的分支将要跳到的地方。指令译码逻辑接受实际的程序指令,并将他们转换成一组基本操作,完成某个简单的计算任务,例如两个数相加。

5.7.2 功能单元的性能

延迟(latency):完成运算需要的时间

发射时间(issue time) :表示两个连续的同类型运算之间需要的最小时钟周期数

容量(capacity):表示能够执行该运算的功能单元的数量

 

posted @ 2021-05-01 22:32  林舸  阅读(155)  评论(0)    收藏  举报