深入理解计算机系统第五章学习-性能优化 1

一、简述

本文主要是通过学习深入理解计算机系统第五章-性能优化之后的理解和总结。第五章主要目的通过对底层硬件架构和机制(汇编、处理器架构)的理解,让程序员写出更加高效(性能)的代码。

二、为什么要进行性能优化

对于性能需要不是很急切的地方进行性能优化的意义不是很大,进行性能优化是需要成本的,需要对原始代码进行重构。随着计算机技术不断发展,处理器性能的提高,往往性能可以通过硬件来弥补,但是有些时候需要对在软件层面进行性能优化。比如大数据处理、多媒体处理、网络传输、数学、算法计算、嵌入式等硬件条件有限的条件下对于性能优化提出更高的要求。

性能提升意味着用户体验的提升,往往一个软件的成败就是在于这些用户体验细微的差别,例如是网络多媒体播放器,如果传输视频卡一些,也许这个软件就会被别人所抛弃。优酷的成功就在于:顺畅的播放体验及强大的资源。

提升软件性能需要各个方面的努力,硬件支持(处理器、内存)、GCC编译器、上层应用软件的开发。在同样的硬件条件下,软件如何提升?在多数的教科书中,往往是介绍了GCC的优化,但是GCC的优化是有局限的,它是以“安全”(保证原始软件功能的前提下)进行的优化,它是一种通用的优化,对于GCC自己不确定的,就默认为不优化。

软件的优化分为几个层级:

1.高级优化:数据结构、高级算法的优化。

2.基本编码原则,消除连续的函数调用,消除不必要的存储器引用。

3.低级优化,底层源码级优化,可根据具体处理器,展开循环,累计变量和重新结合,用功能的风格重写条件操作。

三、能够学到的东西

 在这里主要是介绍低级优化的层级,不改变原始软件的数据结构及高级算法,只针对源码进行较为底层的优化。通过一些技术可以让GCC更好识别源码,编译出性能更高的源码。

四、基本概念

 CPE(Cycles Per Element),每个元素的周期数,每个时钟周期的时间是时钟频率的倒数,一般用纳秒标示。

五、优化方法

GCC优化建议利用-O1以上的,通过对编译器选项的配置可以达到性能的优化。

优化基础程序如下,combine1是我们优化的基础程序,通过对它的优化,讲述优化的功能和效果。

#include <stdio.h>

#include <time.h>

#include <stdlib.h>

 

typedef float  data_t;

#define IDENT 1

#define OP *

 

typedef struct{

long int len;

data_t *data;

}vec_rec,*vec_ptr;

 

vec_ptr new_vec(long int len)

{

vec_ptr result = (vec_ptr) malloc(sizeof(vec_rec));

if(!result)

return NULL;

result->len = len;

if(len > 0){

data_t *data = (data_t *)calloc(len,sizeof(data_t));

if(!data){

free((void *)result);

return NULL;

}

result->data = data;

}

else

result->data = NULL;

return result;

}

 

int get_vec_element(vec_ptr v,long int index,data_t *dest)

{

if(index < 0 || index >= v->len)

return 0;

*dest = v->data[index];

return 1;

}

 

long int vec_length(vec_ptr v)

{

return v->len;

}

 

void combine1(vec_ptr v, data_t *dest)

{

long int i;

*dest = IDENT;

for(i = 0;i < vec_length(v);i++){

data_t val;

get_vec_element(v,i,&val);

*dest = *dest OP val;

}

}

 

int main()

{

struct timeval tpstart,tpend;

float timeuse;

vec_ptr a = new_vec(100000);

data_t *dest = (data_t*)malloc(sizeof(data_t));

printf("test the psum1 time :\n");

gettimeofday(&tpstart,NULL);

combine1(a,dest);

gettimeofday(&tpend,NULL);  

        timeuse = 1000000*(tpend.tv_sec-tpstart.tv_sec) + tpend.tv_usec - tpstart.tv_usec;

        timeuse/=1000000;

        printf("used time:%f\n",timeuse);

return 0;

 

}

 

如果采用-O1选项进行优化,效果对比为:

gcc -O1 test.c -o test

下图是书中分析数据:


如下是在我的兆芯开发版上实验结果:

test1_O0_add_int          0.003695

test1_O2_add_int          0.000283

test_O1_add_int           0.001759

 

这就告诉我们以后编程序的时候至少添加O1选项,这样会使程序性能得到提升。

5.1消除循环的低效率

void combine2(vec_ptr v, data_t *dest)

{

long int i;

*dest = IDENT;

long int length = vec_length(v);

for(i = 0;i < length;i++){

data_t val;

get_vec_element(v,i,&val);

*dest = *dest OP val;

}

}

将vec_length()函数拿到循环外面,性能得到提升:


本机测试结果:

test1_O0_add_int          0.003695

test2_O0_add_int          0.003246

5.2减少过程调用

combine2中有函数get_vec_element(),这个函数实质是提取元素值,可以转换成获取地址,然后取值的方式提取,就减少了循环内部函数调用。

void combine3(vec_ptr v, data_t *dest)

{

long int i;

*dest = IDENT;

long int length = vec_length(v);

data_t *data = get_vec_start(v);

for(i = 0;i < length;i++){

*dest = *dest OP data[i];

}

}

性能分析如图:


本机测试:

test2_O0_add_int          0.003246

test3_O0_add_int          0.001576

 

告诉我们尽量减少循环内部的函数调用。

5.3消除不必要的存储器引用

循环内部尽量减少对指针的引用,可以在循环体外面替换为变量的形式。因为在循环体内部,使用引用的形式,需要从存储器中调用数据加载到寄存器,然后对寄存器数据处理,之后再把寄存器的内容存放到存储器,过程较为复杂。如果采用临时变量的形式可以提高软件的性能。

void combine4(vec_ptr v, data_t *dest)

{

long int i;

long int length = vec_length(v);

data_t *data = get_vec_start(v);

data_t acc = IDENT;

for(i = 0;i < length;i++){

acc = acc OP data[i];

}

*dest = acc;

}




性能分析:


本机测试:

test3_O0_add_int          0.001576

test4_O0_add_int          0.001313

能够利用变量代替就别用指针,利用引用操作会造成编译器编译的汇编代码为加载存储寄存器,降低了性能。

5.4理解现代处理器

到目前为止,我们运用的优化都不依赖于目标机器的任何特性,这些优化只是简单地降低了过程调用的开销,以及消除了一些重大的“妨碍优化的因素”,这些因素会给优化编译器造成困难。随着试图进一步提升性能,我们必须考虑利用处理器微体系结构的优化,也就是处理器用来执行指令的底层系统设计。要想获得充分提高的性能,需要自信地分析程序,同时代码的生成也要针对目标处理器进行调整。尽管如此,我们还是能够运用一些基本的优化,在很大一类处理器上产生整体的性能提高。


上图表示了处理器大概的处理过程,针对不同处理器的体系架构,优化处理的方法可能不一样。例如兆芯nano CPUIntel的结构是不一样的,主要不同有:

1.预测分支结构不同。

2.缓存大小及结构不同。

3.多核的结构不同。

4.逻辑处理单元不同。(加法器个数不同)

5.不同指令集执行的时间不同。

这些的不同导致对于不同的处理器上,优化程序的方法不一样。


如图是Intel I7的逻辑处理功能单元的性能,包括延迟及发射时间,根据它以及源码就可以估计程序的性能。一般CPU的逻辑处理功能单元的性能会在官方网站上给出。

 

数据流图分析程序性能,例如combine4

void combine4(vec_ptr v, data_t *dest)

{

long int i;

long int length = vec_length(v);

data_t *data = get_vec_start(v);

data_t acc = IDENT;

for(i = 0;i < length;i++){

acc = acc OP data[i];

}

*dest = acc;

}



循环体是影响执行时间的主要因素,对于一个循环体内部只读、只写不产生循环相关的寄存器不对程序起主导因素,只有形成循环相关的寄存器才起到重要影响(就是从本次循环到下一次读写并且产生前后相关的操作的寄存器),如图a,b经过简化就找到了影响combine4的主要限制因素。


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值