深入理解计算机系统(5.1)------优化程序性能

  你能获得的对程序最大的加速比就是当你第一次让它工作起来的时候。

  在讲解如何优化程序性能之前,我们首先要明确写程序最主要的目标就是使它在所有可能的情况下都能正常工作,一个运行的很快的程序但是却是错误的结果是没有任何用处的,所以我们在进行程序性能优化之前,首先要保证程序能正常运行,且结果是我们需要的。

  而且在很多情况下,让程序跑的更快是我们必须要解决的问题。比如一个程序要实时处理视频帧或者网络包,那么一个运行的很慢的程序就不能解决此问题。再比如一个计算任务计算量非常大,需要数日或者数周,如果我们哪怕只是让它运行的快20%也会产生重大影响。

1、编写高效程序的切入点

  ①、选择一组合适的算法和数据结构。

  ②、编写出编译器能够有效优化以转换成高效可执行的源代码。

  ③、多线程并行处理运算。

  对于第一点,程序=数据结构+算法,选择合适的数据结构和算法无疑对于提高程序的运行效率有很大的影响。第二点对于编程者则需要理解编译器的优化能力以及局限性,编写程序看上去只是一点小小的改动,可能都会引起编译器优化方式很大的变化;第三点技术主要这对运算量特别大的运算,我们将一个大的任务分成多个小任务,这些任务又可以在多核和多处理器的某种组合上并行的计算,这里我们也需要知道,即使是利用并行性,每个并行的线程都要以最高性能的方式执行。

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

  正确性,正确性,正确性!!!这个要着重提醒,所以编译器必须很小心的对程序使用安全的优化。限制编译器只进行安全的优化,会消除一些造成错误的运行结果,但是这也意味着程序员必须花费更大的力气写出程序使编译器能够将之转换为有效机器代码。

  对于下面两个程序:

void add1(int *xp,int *yp){
     *xp += *yp;
     *xp += *yp;
}

  

void add2(int *xp,int *yp){
     *xp += 2* *yp;
}

  对上上面两个函数add1和add2,它们都是将存储在由指针 yp 指示的位置处的值两次加到指针 xp 指示的位置处的值。但是明显add2的执行效率要高,它只要求 3 次存储器的引用(读*xp,读*yp,写*xp),而add1需要 6 次存储器引用(2次读*xp,2次读*yp,2次写*xp)。

  因此,如果编译器要优化add1,我们可以认为add2是其优化后的代码。但实际上真的是这样吗?如果 xp 等于 yp,那么变成如下:

void add1(int *xp,int *xp){
     *xp += *xp;
     *xp += *xp;
}

   

void add2(int *xp,int *xp){
     *xp += 2* *xp;
}

  我们可以看到,这个时候对于 add1,xp的值会增加 4 倍,但是 add2 当中,xp 的值只增加 3 倍。由于编译器不知道参数 xp 和 yp 是否相等,它必须假定他们有可能相等,所以不会产生 add2 作为 add1 的优化版本。

  在各种编译器中,我们前面说过的 gcc 编译器,可以通过加参数O0 -->> O1 -->> O2 -->> O3,分别是从没有优化到优化级别最高。但是基本上编译器都不会对程序进行各种激进的优化,所以程序员必须以一种简化编译器生成高效代码的任务来编写程序。如何编写,请接着往下面看。

3、程序的性能表示

  处理器活动的顺序是由时钟控制的,时钟提供了某个频率的规律信号,通常用千兆赫兹(GHz),即十亿周期每秒来表示。例如,当表明一个系统有“4GHz”处理器,这表示处理器时钟运行频率为 4*109 千兆赫兹。每个时钟周期的时间是时钟频率的倒数。通常用纳秒(nanosecond,1 纳秒等于10-9秒),或者皮秒(picosecond,1 皮秒等于10-12秒)来表示,一个 4GHz 的十周周期为0.25纳秒,或者说250皮秒。从程序员的角度来看,用时钟周期来表示度量标准要比用纳秒或者皮秒来表示有用的多。

  用时钟周期来表示,度量值表示的是执行了多少条指令,而不是时钟运行的有多快。

4、提高程序的性能方法

  这本书的作者讲解如何优化程序性能主要从两个方面入手,第一个是与机器无关,第二个是与机器相关。

  与机器无关:

  ①、消除循环的低效率:将每次循环中执行多次但计算结果不改变的部分提出循环,这样只需计算一次,而不用循环一次,计算一次。以此提高算法效率。

  ②、减少过程调用:也就是减少函数方法的调用,因为函数方法的调用会带来相当大的开销。但是这样也会带来一个缺点,就是破坏程序的模块化,所以我们需要权衡利弊。

  ③、消除不必要的存储器引用:在循环中不停地对指针所指向的变量赋值的时候,我们可以用一个中间变量代替指针,以增加速度。

  ④、选择合适的算法和数据结构:为遇到的问题选择合适的算法和数据结构,避免使用产生糟糕性能的算法或编码技术。

  与机器相关:

  ①、理解现代处理器

  在代码级上,看上去似乎是一次执行条指令,每条指令都从寄存器或存储器中取值,执行一个操作后,并把结果存到一个寄存器或存储器位置。但是实际上,在处理器中是同时对多条指令求值,称为指令级并行。现代微处理器了不起的成就就是它们采用复杂而奇异的微处理结构,多条指令可以并行执行,同时又呈现出一种简单的顺序执行指令的表象。

  当一系列操作必须按照严格的顺序执行时,就会遇到延迟界限,因为在下一条指令开始之前,这条指令必须结束。当代码中的数据相关限制令处理器利用指令级并行的能力时,延迟界限能够限定程序性能。吞吐量界限刻画了处理器功能单元的原始计算能力。这个界限是程序性能的终极限制。

  下图是一个现代微处理器的简化示意图:

  

  指令控制单元(Instruction Control Unit,ICU)从指令高速缓存(instruction cache)中读取指令,并产生一系列基本操作。指令高速缓存是一个特殊的高速缓存存储器,它存储最近访问的指令。通常ICU会在当前正在执行的指令很早之前取指,这样它才有足够的时间对指令译码,并把操作发给执行单元 EU(Execution Unit ,EU),然后由EU完成ICU产生的基本操作。

  ②、提高并行性

  循环分割,利用功能单元的流水线化的能力提高代码性能。对于一个可结合和可交换的合并操作来说,比如说整数加法和乘法,我们可以通过将一组合并操作分割成两个或更多的部分,通过在最后合并结果来提高性能。

时间: 2024-08-29 03:08:28

深入理解计算机系统(5.1)------优化程序性能的相关文章

《深入理解计算机系统》 优化程序性能的几个方法

本文几个优化程序性能的方法出自CSAPP第五章,通过不断修改源代码,试图欺骗编译器产生有效的代码 我们先引入度量标准每元素的周期数(CPE),表示程序性能. 我们先定义一个数据结构   data_t 代表数据类型 1 typedef struct{ 2 long len; 3 data_t *data; 4 }vec_rec,*vec_prt; 以及常数IDENT和OP以便在后续的代码中进行不同的操作 //对所有向量的元素求和 #define IDENT 0 #define OP + //对所有

浅谈优化程序性能(下)

前言 在上一篇随笔中,我们谈到最小化一个计算中的操作数量不一定会提高它的性能.现在,就让我们来解开为什么会出现这种情况的原因吧. 处理器体系结构 在计算机的处理器中,处理一条指令包括很多操作,可以分为取指(fetch).译码(decode).执行(execute).访存(memory).写回(write back)和更新程序计数器(PC update)等几个阶段.这些阶段可以在流水线上同时进行,如下图所示: 上图中,F.D.E.M 和 W 分别代表上述五个阶段.当然,现代的处理器比这个示例要复杂

优化程序性能(CSAPP:5)

[前言]虽然现在没有接触过大型项目,但是工作了会注重性能.学习一下,应该能更好更快的理解别人写的经典优秀的代码.结合CSAPP和自己的理解,总结一下. 一.程序优化综述 1.高效程序的特点 (1)适当的算法和数据结构.方法和数据的组织形式无疑是最关键的,是优化的基础: (2)代码能够被编译器转化成高效的可执行代码.需要深入了解使用的编译器的优化方法,和常见的优化策略: (3)运用现代并行编程技术.多核以及硬件支持提供更大的加速可能,例如GPU: 2.优化程序的一般步骤 (1)消除不必要的工作,例

《深入理解计算机系统》3.2程序编码

程序编码 在Unix机上使用命令 编译代码 优化编译的等级越高,编译时间越长,程序性能越高 机器级代码 对于机器级编程,两种抽象尤为重要 第一种 机器级程序的格式和行为(ISA指令集体系结构) 第二种 机器级程序使用的存储器地址是虚拟地址 机器级代码把存储器看成一大块数组罢了 汇编代码案例 具体的代码作用是后面章节才讲的 学完寄存器和栈后才画出的图,书上某些部分例子没有讲全,搜了很多资料,可能这些部分是再靠后的章节才讲 相关资料链接 http://www.cnblogs.com/jiu0821/

浅谈优化程序性能(上)

前言 我们知道,多项式定义为: 在几何学中,多项式是最简单的平滑曲线.简单是指它仅由乘法及加法构成,平滑是因为它类同口语中的平滑,以数学术语来说,它是无限可微,即它的所有高次微分都存在.事实上,多项式的微分也是多项式.简单及平滑的特点,使多项式在数值分析.图论,以及电脑绘图等,都发挥极大的作用.多项式求值是解决许多问题的核心技术.以数值分析为例,多项式函数常常用作对数学库中的三角函数求近似值. 现在,让我们来用 C 语言写一个对多项式求值的函数吧. 直接的算法 直接按照多项式的定义使用循环求值:

记一次使用ConcurrentDictionary优化程序性能的经验总结

项目情形 最近做项目发现有个业务逻辑性能效率巨慢, 实际上是扫描cosmos上面16个文件夹下面的数据, 每个folder下面大概分为100来个对应user的fodler, 然后对应user folder下面存放的是user的数据. 原逻辑是一个folder一个folder去scan, 然后将统计的数据按照 user和size存放到一个dictionary中, 最后汇总统计并且发邮件. 其中影响效率的部分有当前运行环境与cosmos的交互上, 不同的环境快慢不同. 另外一个就是code逻辑是串行

优化程序性能(3)——提高并行性

在之前的学习中,程序的性能是受运算单元的延迟限制的.正如我们表明的,执行加法和乘法的功能单元是完全流水线化的,这意味着它们可以每个时钟周期开始一个新操作,并且有些操作可以被多个功能单元执行.硬件具有以更高速率执行乘法和加法的潜力,但是代码不能利用这种能力,即使是使用循环展开也不能,这是因为我们将积累值放在一个单独的变量acc中,在前面的计算完成之前,都不能计算acc的新值(顺序依赖).虽然计算acc值的功能单元能够每个时钟周期开始一个新操作,但是它只会每L(L是合并操作的延迟)个周期开始一条新操

web应用程序性能优化

web应用程序基本上都是在浏览器地址栏输入一段网站,然后进入,最后浏览器显示你想要的东西. 这就是用户所能体会到的东西.那作为程序员我们看到了什么呢? 一次HTTP 请求主要的流程是: 1.DNS服务器解析域名(浏览器地址栏的地址)获取相应的IP地址.端口号. 服务名. 2.客户端根据解析后的地址向服务啊发送请求(建立与服务器的联接). 3.服务器根据用户的请求信息处理请求,并做出响应. 4.浏览器更具服务器响应的数据(HTML/css/js)渲染页面. 那要优化程序性能,作为程序员我们能优化哪

iOS 程序性能优化

前言 转载自:http://www.samirchen.com/ios-performance-optimization/ 程序性能优化不应该是一件放在功能完成之后的事,对性能的概念应该从我们一开始写代码时就萦绕在我们脑子里.了解 iOS 程序性能优化的相关知识点,从一开始就把它们落实到代码中是一种好的习惯. 初级技巧 使用复用机制 在我们使用 UITableView 和 UICollectionView 时我们通常会遇到「复用 Cell」这个提法,所谓「复用 Cell」就是指当需要展示的数据条