HIT-CSAPP 第五章 面向程序的优化方法(1)

5.1:

1.面向程序性能的优化

  • 面向编译器的程序优化方法:减少过程调用、减少内存引用、指令并行等方法等方法。
  • 面向流水线、超标量、向量CPU的程序优化方法。

2.存储器的层次结构

29370dc3fea242eebfa17824377c3cfd.png

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

内存别名使用妨碍函数优化

void twiddle1(long *xp, long *yp){ //假设传入的参数是1 2 
    *xp + = *yp;                    //x = x+y=3
    *xp + = *yp;                    //x =x+y=5
}

第一段代码,函数从内存中引用数据需要访问内存两次,然后执行加的操作往内存中写还需要访问依次内存,这就是三次。然后下一句x = 5又访存了三次所以一共访存了六次。

void twiddle2(long *xp, long *yp){ //假设传入的参数是1 2 
    *xp + =2* *yp;                    //x = x+2y=5
}

第二个函数他把访存六次变成了访存三次,但是在实际运行中编译器是否会采用第二种方式呢?

b7a273eecc1f43db888fb3189dcf136c.png

 采用第二种方式往往得不到我们想要的结果:假设xp和yp指针指向内存的同一位置,那么在下面这种情况下,可能和预期不符。

///twiddle1执行的效果:扩大四倍///
*xp += *xp;
*xp += *xp;

//twiddle2执行的效果扩大三倍///
*xp += 2**xp;

事实上,编译器为了避免xp和yp相等的情况,不会产生类似twiddle2的代码进行优化。

再看书上的一个例子:

x = 1000, y=3000
*q=y;/*3000*/
*p=x;/*1000*/
t1=*q;/*1000 or 3000*/

两个指针指向同一位置的情况叫做内存别名使用,如果指向同一位置那么就是1000指向不同位置那么就是3000,编译器必须假设两种情况,这就限制了编译器的优化。

练习5.1

d1457d7205da4cb4922d3b45ee6874c0.png

如果相等
*xp = *xp+*xp //2x
*xp = *xp-*xp//0
*xp = *xp-*xp//0

递归调用

4c37d7c84443445fb42aedfb46e4fa53.png

 内联函数对函数调用的优化

表示程序的性能

  • CPE:每元素的周期数
  • CPI:每指令的周期数
  • 延迟界限:当一系列指令严格按照顺序执行,执行下一条指令前,上一条指令必须完成
  • 吞吐量界限:是程序性能的终极界限,刻画处理器单元的原始计算能力。

一般有用的优化

考虑编译器

641688f6972f4ecdaa8162ac65a2b00d.png

48301cb23d764c0f93ac57f3aa7f73fe.png

 不考虑编译器

1.代码移动

如果他总是产生相同的结果,将代码从循环中移出。

acfd1e39e4d3484a94aa3b0f95a00a87.png

4daa4f5c69cf4921b73ac1204602198e.png

 2.复杂运算简化

  • 比如可以用移位代替乘法:乘法需要三个时钟周期而移位只需要一个时钟周期。
  • 将乘法替换成加法如下图蓝色笔所示,演示的乘法和加法是等价的,性能提高了三倍

415139ab68464e67b3baf078827173c1.png

 3.共享公共子表达式

-og 基本的优化 -o1 -o2更高级别的优化

adc2e2c900314873a777c70581e48124.png

 都有i*n+j的表达式,汇编语言掌握即可。

程序员角度优化程序

减少过程调用

3b4fc78bcd004d2297ce43398526de96.png

c3c0e09ef8634efdbca67652d2bb4581.png

329748d9a91d4c15a8a0c7f9d861a6e9.png

 为什么编译器不能自动代码移动&&程序员如何提高程序性能

c8e945d6c452426f8278001a7bc4d0e6.png

 消除不必要的内存引用

6c114abd7a70494e9891412a3ca2a7bf.png

 CPU和内存之间速度差距很大,所以第六章介绍了多级缓存结构,但是每次CPU访问主存还是要花费时间的,从程序员的角度,可以尝试减少访问时间。

9198f1d5ecf64646acd5782a894d8dc5.png

 b[i]--------每次都要读出来再写回 为啥编译器不能优化? 内存别名

49daff8b16f445d08c37ee0b87c163a5.png

 由第七章的知识可知局部变量是存放在栈中,局部变量运行的时候往往先存放在cache中

b5d7d3e8c9954475a240a9a1ab46b376.png

 这样优化完之后,就将要修改的值存入cache,减少访问内存的次数,增加对缓存的访问,进而提高程序的速度。

练习5.4

44b19f2724664f1784d5c40e81f665e7.png

cf440bcd8559457db61c56739d8a3bf0.png  

 A:没经过优化的代码中,%xmm0简单地被用作临时值,每次循环迭代中都会设置和使用,

B:两个版本有相同的功能,甚至内存别名的使用

C:变换可以不改变程序的行为,因为除了第一次迭代开始从dest读取值和前一次迭代最后写入到这个寄存器的值是相同的。因此,合并指令可以简单第使用在循环开始时就已经在%xmm0中的值。

两个函数的区别是,第二个函数访问的次数少,第一个没必要读内存,其实只需要把有用的数据写入内存就行

利用执行集并行

Benchmark例子:向量的数据类型

386814d737fc48b38c0378c9eaeedfc3.png

4d5661b1724046298455c27621499d9d.png

                                                        减少内存引用

采用流水线方式工作

4fed71a02e9749f384573fe31086f6e8.png

5.8循环展开:

循环展开通过每次增加函数循环迭代的个数,减少迭代的次数,也可以减少关键路径的数量。

148f347678234c6088eef160c27b2d90.png

 但是优化之后不能明显提高程序性能,我们分析一下原因。

dd6d7764565f4cfd87f5b0abe614753b.png

 练习5.8画关键路径

1a7acb9d718c4522b7069a3b940178a0.png

a7e8253e194546c2a6248c17050919e0.png

6c9bb95563b947fab58c3a8f466a234c.png

41654e3e13f24bcc9f0ae86bcaa29d9b.png

问题:

内联函数 减少内存访问次数的过程

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值