最近看了《深入理解计算机系统》这本书,对其中程序优化这一章节进行了深入学习,以此博客作为学习记录。

先总结几点程序优化的原则:

1.     代码正确问题,一个运行很快但是给出了错误结果的程序没有任何用处

2.     代码清晰简洁,这样不仅是为了自己看懂代码,也是为了检查代码和今后需要修改时,其他人能够读懂和理解代码

3.     考虑代码的使用方式和影响它的关键因素,低级别的代码优化没有带来较大的性能提高反而使得程序的可读性和拓展性下降,更容易出错了,这就不是一个理想的代码优化。我们应当选择那些性能重要的环境下反复执行的代码进行大量的优化。

程序高效程序可以从以下几个方向出发:

1、 选择一组适当的算法和数据结构。

2、 编写出编译器能有效转化成高效可执行代码的源代码。这对理解优化编译器的能力和局限性要求比较高

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

了解编译系统如何工作是大有益处的;

1、优化程序性能。(为了我们在C程序中做出好的代码选择我们确实需要对汇编语言以及编译器如何将不同的C语句转化为汇编语言有一些基本的了解)

2、理解连接时出现的错误。(一些最令人困扰的程序错误往往都与链接器操作有关,尤其是当你试图建立大型的软件系统时)

3、避免安全漏洞。(近年来,缓冲区溢出错误造成大多数网络和Internet服务器上的安全漏洞,这些而错误的存在时因为太多的程序员忽视了编译器用来为函数产生代码的堆栈规则)

首先在学习控制之前先要了解编译器,现代编译器运用复杂精细的算法来确定一个程序的计算值,以及它们是如何被使用的,大多数编译包括GCC向用户提供了一些对它们所使用的优化的控制。最简单的控制就是指定优化级别,例如用命令“-Og”调用GCC就是让GCC只做基本的优化,而-O1或者更好的优化级别都是可以支持的。虽然对于大多数GCC软件项目来说-O2已经成为了可以被接收的标准,但是主要还是考虑以优化级别为-O1编译出的代码。在以此为基础下,我们做了接下来的对比测试。

举一个简单的例子,考虑以下代码

void test1(long *xp,long *yp)

{

*xp+= *yp;

*xp+= *yp;

}

void test2(long *xp,long *yp)

{

*xp+= *yp * 2;

}

我们能知道test1,使用6次内存读写,而test2只用了三次内存读写,因为我们会以为test1会优化为test2版本,然而实际上并没有,考虑到当xp=yp的时候,test1计算结果了4倍*xp,而test2只计算出3倍*xp,而编译器在编译时不知道该函数会被如何调用故不会产生test2的优化。

(结论:在只执行安全的优化中,编译器必须假设不同的指针可能指向同一位置)

函数调用会妨碍优化,考虑以下代码

long f();

long test1()

{

return f() + f() + f() + f();

}

long test2()

{

return 4 * f();

}

看上去两个过程会产生相同结果,但是test2只调用一次f(),而test1调用了4次,这让我们很容易联想到test2作为test1的优化,但是如果考虑f()为以下情况

int g_count = 0;

long f()

{

return g_count++;

}

这是两个函数就会产生不同的全局变量值,所有编译器不会进行优化,大多数编译器不会试图判断一个函数有没有副作用,如果没有则进行优化,而相反的,编译器会假设最糟的情况,保持所有函数调用不变。在各种编译器中,就优化能力来说,GCC是认为胜任的,但是不是特别突出,它只带基本的优化,而不会对程序进行更加“有进取心的”编译器所做的那种激进变换,因为使用GCC的程序员需要花费更多精力以简化编译器生成搞笑代码的任务的方式来编写程序。

在了解了编译器的局限性之后,我们学会来表示程序的性能

再次我们引入度量标准,每周期的元素数(Cycles Per Element ,CPE)作为一种表示程序性能并指导我们改进代码的方法。通过最小二乘拟合的方法来计算CPE。接下来我们以一段短小代码为例,演示如何程序的优化过程

先定义一个向量数据结构,内存由头部和数据数组构成,以下声明一个头部结构。

typedef struct {

long len;

data_t * data;

}vec_rec, *vec_ptr;

Data_t代码基本数据类型,我们可以声明不同基本类型进行测试

typedef long data_t;

如上设置long类型,还会分配一个len个data_t类型对象的数组,存放实际元素。

首先编写类型申请函数

vec_ptr new_vec(longlen)

{

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

data_t*data = nullptr;

if(!result)

return nullptr;

result->len= len;

if(len>0)

{

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

if(!data)

free((void*)result);

}

result->data= data;

return result;

}

然后我们考虑这个向量数据结构的基本功能函数包括下标访问和长度取出。

intget_vec_element(vec_ptr v, long index, data_t*dest)

{

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

{

return -1;

}

*dest= v->data[index];

return 0;

}

longvec_length(vec_ptr v)

{

return v->len;

}

然后我们对数据进行运算,声明常数BEGIN和运算符OP

#define IDENT 0

#define OP +

void combine1(vec_ptrv, data_t *dest)

{

*dest= IDENT;

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

{

data_tval;

get_vec_element(v,i, &val);

*dest= *dest OP val;

}

}

可以观察到 combine1中每次调用vec_length作为测试条件,因为函数中向量长度不会随着循环的进行二改变,因此只需要计算一次向量长度,用临时变量记录即可,然后再测试条件中都可以使用该值,

void combine2(vec_ptrv, data_t *dest)

{

*dest= IDENT;

intsize = vec_length(v);

for(int i = 0; i < size; i++)

{

data_tval;

get_vec_element(v,i, &val);

*dest= *dest OP val;

}

}

这就是一个典型的以代码移动为基础的优化。然后我们考虑减少过程调用,每次循环都会调用get_vec_element这个函数,每次引用都会边界检查,很容易造成低效率,我们明显能知道边界是合法的,于是我们去除边界检查。

data_t*get_vec_start(vec_ptr v)

{

returnv->data;

}

void combine3(vec_ptrv, data_t *dest)

{

intsize = vec_length(v);

data_t*data = get_vec_start(v);

*dest= IDENT;

for(int i = 0; i < size; i++)

{

*dest= *dest OP data[i];

}

}

最后我们来消除不必要的内存引用,因为每次迭代的时候第i个元素都会保存指针在寄存器中,下一个循环又从寄存器取出放入内存中,这样读写浪费我们的性能,于是我们通过临时变量来在循环中累积计算出来的值。

void combine4(vec_ptrv, data_t *dest)

{

intsize = vec_length(v);

data_t*data = get_vec_start(v);

*dest= IDENT;

data_tval;

for(int i = 0; i < size; i++)

{

val= val OP data[i];

}

*dest= val;

}

至此我们基本完成这段程序的优化。可能有人认为combine3到combine4这个过程编译器能够实现,但是实际上,由于内存别名的使用,这两个函数会有不同的行为,假设

combine3(v,get_vec_start(v) + 2);

combine4(v,get_vec_start(v) + 2)

其中combine3在循环过程中,dest的地址问题,导致v的第三个元素不断发生变换,执行结果大相径庭,所以考虑到这个情况,编译器不能判断函数会在什么情况下被调用,也就不会进行优化,这需要我们来认为设计函数的优化程度了。

参考资料:《深入理解计算机系统》

主要内容大概就是以上,通过对编译器的局限了解,我们试着利用机器的思维来优化我们的程序,多想想看是什么制约的程序的性能,这样我们的代码就能越写越好,下期再见-。-

---来自新年新气象的Racoon

程序性能优化之编译器篇(Racoon)相关推荐

  1. 小程序性能优化之页面预加载方案——让你的小程序运行如飞 进阶篇

    小程序性能优化之页面预加载方案 进阶篇 转载请注明出处:https://blog.csdn.net/sinat_27612147/article/details/80798452 写在前面 预加载方案 ...

  2. 小程序性能优化之页面预加载方案——让你的小程序运行如飞 集成篇

    小程序性能优化之页面预加载方案 集成篇 转载请注明出处:https://blog.csdn.net/sinat_27612147/article/details/80802725 前言 之前看到一篇文 ...

  3. 原来 CPU 为程序性能优化做了这么多

    来自:武培轩 本文主要来学习内存屏障和 CPU 缓存知识,以便于我们去了解 CPU 对程序性能优化做了哪些努力. 首先来看下 CPU 缓存: CPU 缓存 CPU 缓存是为了提高程序运行的性能,CPU ...

  4. 《C++应用程序性能优化::第五章动态内存管理》学习和理解

    <C++应用程序性能优化::第五章动态内存管理>学习和理解 说明:<C++应用程序性能优化> 作者:冯宏华等 2007年版. 2010.8.29 cs_wuyg@126.com ...

  5. C++应用程序性能优化(三)——C++语言特性性能分析

    C++应用程序性能优化(三)--C++语言特性性能分析 一.C++语言特性性能分析简介 通常大多数开发人员认为,汇编语言和C语言比较适合编写对性能要求非常高的程序,C++语言主要适用于编写复杂度非常高 ...

  6. C++ 应用程序性能优化,第 6 章:内存池

    引言 本书主要针对的是 C++ 程序的性能优化,深入介绍 C++ 程序性能优化的方法和实例.全书由 4 个篇组成,第 1 篇介绍 C++ 语言的对象模型,该篇是优化 C++ 程序的基础:第 2 篇主要 ...

  7. 《C++应用程序性能优化::第二章C++语言特性的性能分析》学习和理解

    <C++应用程序性能优化::第二章C++语言特性的性能分析>学习和理解 说明:<C++应用程序性能优化> 作者:冯宏华等 2007年版.最近出了新版,看了目录,在前面增加了一章 ...

  8. 一文了解 Java 应用程序性能优化指南

    点击上方"CSDN",选择"置顶公众号" 关键时刻,第一时间送达! 在<2018 最具就业前景的 7 大编程语言>一文中,通过分析了来自 Indee ...

  9. Unity性能优化 :合批篇

    前言 本系列为一些性能优化的小知识,是日常游戏开发中与性能表现的一些点,本篇为该系列文章的第二篇,前篇链接: 第一篇: Unity性能优化:资源篇 在早期Unity中,对于合批的处理手段主要是下面三种 ...

  10. Java程序性能优化——设计优化

    原文出自:http://blog.csdn.net/anxpp/article/details/51914119,转载请注明出处,谢谢! 1.前言 OK,之前写了一篇文章:"23种设计模式介 ...

最新文章

  1. 产品中的实名认证该怎么设计?
  2. sublime关于行操作的快捷键:如快速复制整行等
  3. wnoise matlab,MATLAB中用wnoise函数测试去噪算法
  4. android 系统(7)---android框架大全
  5. 【流媒體】live555—VS2010 下live555编译、使用及测试
  6. Linux的哲学思想
  7. 5.3 GRU、LSTM 情感分类
  8. Maven入门指南12:将项目发布到私服
  9. k8s pod部署到不同node_应用部署演进(二)
  10. MySQL 入门(二)—— MySQL理论基础
  11. 测试网络机顶盒的软件,新买的网络机顶盒安装什么软件好 几款热门装机必备软件分享...
  12. VS201x的项目属性配置
  13. 计算机主机爆炸,意外:插入计算机后,主机的电源就会烧断。发生了什么?计算机电源爆炸了吗?...
  14. Footprint Analytics: 从多个维度带你进入 GameFi 领域
  15. 华硕主板刷机后不能进入Windows的解决办法
  16. 程序员转行有哪些方向?人到中年,不能当一辈子普通程序员吧!
  17. 这个牛逼的在线项目任务管理工具,终于开源了!
  18. yandex注册验证码怎么填_注册163邮箱格式怎么填?163电子邮件注册格式
  19. 华为路由器显示连接到服务器失败怎么办,华为路由WS5200可以搜到wifi但无法连接怎么办...
  20. Clickhouse 字符串函数

热门文章

  1. 3.5 计算机网络之介质访问控制(静态划分信道、FDM、TDM、STDM、WDM、CDM)、(动态划分信道、ALOHA、CSMA、CSMA/CD、CSMA/CA)、令牌传递协议
  2. 如何编写c语言延时程序,单片机写延时程序的几种方法
  3. Inna and Alarm Clock
  4. 2022-07-17 第四小组 孙翰章 职业规划
  5. EndNote论文参考文献中文文章与英文文章前后顺序修改排列方法
  6. Android studio使用SVN
  7. (9)LICEcap——PC端动图创建工具
  8. 【用户角色权限设计】
  9. Trister Community DAOs最新型DAO架构
  10. 导航卫星系统实时可视化平台开发