主要看了第5章,优化程序性能。如同老师所说的,程序优化的技巧是须要平时不断积累,并且经过阅读这一章,充分体会到,真正想要作到程序的优化,下到计算机体系结构,汇编语言,同时对编译器要有充分的了解,上到合适的数据结构以及算法的选择都会对程序的运行速度产生巨大的影响,除外还有任务的的切割,进行并行运算。可是这一章它主要仍是讲的是咱们应当如何正确的编写代码来引导编译器进行优化,仍是颇有意思的。最优秀的是做者使用的测试cpu是intel core i7 Haswell,让我感受到这些技巧确确实实如今仍旧有意义。算法
所谓的局限性是编译器可能不会像你想象中的作一些理想固然的优化,由于编译器必须保证绝对的安全性,举了一个很简单的例子安全
void twiddle1(long *xp ,long * yp){ *xp+=*yp; *xp+=*yp; } void twiddle2(long *xp ,long * yp){ *xp=2*yp;; }
并且咱们按的认为1,2等效,并且2更高效一点,由于内存读写数目少,可是实际上,二者是不等价的,好比xp与yp指向相同元素地址的时候,1变为4倍,2只有三倍。其后还举了函数调用的例子,都是强调,编译器是会自动优化,可是可能并不会为咱们优化掉一些咱们理算固然的部分,由于可能会带来安全的隐患。数据结构
for(int i = 0;i < length; i++){ *dest++; //或者其余运算 } int q =*dest; for(int i = 0;i < length; i++){ q++; //或者其余运算 } *dest = q;
书中为咱们展现了二者的汇编代码,2明显比1少了取dest值到寄存器再从起存起赋值回去的两个语句,速度获得了明显提高,整型CPE降到了原来的1/7,浮点数1/3.而且举了例子,编译器出于安全考虑不会作这样的优化。(可是书中还介绍了GCC优化选项如何在保证安全性的基础上作优化,发现本质上仍是引入了中间值,不过每一次都会更新*dest的值,至关于减小一次取值到寄存器的操做)并发
介绍了一些现代处理器的一些技术,如指令级并行(即如何在底层实现并行运算,可是上层上却显示出顺序执行的结果),分支预测(在条件转移指令处预测转移地址)等等。
比较新奇一点是看到了退役单元的概念,其是一个寄存器文件,储存着最近的指令操做,寄存器更新,只有当一个指令以前全部相关的分支点都确认预测成功了,这条指令就退役,计算结果才真正写入程序下相关的寄存器,也就是说以前用的寄存器都是闲暇的(我猜的,书中写的不是特别清楚),反之,以前有分支预测错误,此指令全部涉及的计算结果所有丢弃。这样就保证了预测错误时结果的正确性。 这样,必然就出现了一个指令之间寄存器值传递的问题,相应的诞生了寄存器重命名机制,可是我没看懂具体是怎么实现的,可是理解了它就是经过一张表来实现寄存器间传递,而不是一条指令写入寄存器,另外一条去读,这样就保证了只有退役指令才能写寄存器。函数
循环展开是并行运算的一个应用,为了讲这个,先是引入了循环的关键路径的概念(其实直接理解为CPE是能够的),就是说,必定程度上一个循环体内的计算语句不是顺序的,而是并行的(循环标志的++与数据的运算,之前历来没有这个概念),通常是数据的浮点运算成为瓶颈,其运算也构成一次循环完成的关键路径,完成循环的时间=关键路径*循环次数,而后做者举了一个例子:性能
for(int i = 0 ; i < length ; i++){ acc=acc op date[i]; } for(int i= 0; i < length ; i=i+2){ acc1 = accc1 op date[1]; acc2 = acc2 op date[i+1]; }
会发现2会比1快接近一倍,为何呢,是由于增长一个累计变量后,其实acc1与acc2的计算基本是并行的(多加了一个功能单元),2中每次循环完成时间与1基本相同,可是循环次数减半了!!!! 固然这种优化也是有上限的,最多展开到相应的功能单元个,也就仍是说,只要相应功能的计算单元流水线满了,就没有展开的余地了。(这就要求咱们要对本身的cpu计算单元有所了解,好比处理整数加法的有多少个,浮点数乘法的有多少个)测试