第5章
在所有情况下都正确是写程序的最主要目标,然后是好的用户接口用户体验,然后是易读性、易扩展易修改性,然后才是性能。这样看性能好像不太重要。
实际上,很多时候性能 不是和上述的这些评价方式并列的,而是这些评价方式的基础。正因为有好的性能,我能才可以浪费一倍在异常处理上,在用户体验上,在扩展修改上。
这一章并不讲算法与数据结构这种提高性能的方式,而是从程序运行角度来展开。这种提高是锦上添花的事情。程序员需要对跑程序的处理机有所了解,知道他们大概是如何工作的,如机器是几核的,是否采用了乱序,从而调整他们的程序以获得最大的速度。这种提高的能力有限,并不能改变原程序的运行时间增长阶。
1理解编译器的能力的局限性
没有任何编译器能用好的算法或数据结构代替低效率的算法或数据结构。编译阶段的代码优化为了保证程序的完全正确性编译,所具有的能力是非常有限的。
2程序的优化实例
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;
}
}
1) 消除循环的低效率;识别出要执行多次,但计算结果不会改变的计算,将该计算移动到循环前面。如上述中的vec_length(v);
因为编译器并不知道vec_length(v)在每次循环中是否改变,它只能按最坏的情况考虑,每次都会重新计算一次。
当v比较小的时候,可能效果不明显。一旦v变大了,vec_length(v)运行所占的时间会明显增多。
2) 减少过程调用;每次循环都会执行一次get_vec_element(v, i,&val);用来得到第i个元素,作为替代 我们在循环前增加一个get_vec_start();在循环里就可以对起始地址进行简单操作,找到所需元素。但这种变换严重损害了程序的模块性。是否需要这样做,将视具体情况而定。
3) 消除不必要的存储器引用;*dest = *dest OP val; 由上一章知道,指针会被编译为存放在寄存器中的间接地址引用,即引用这个变量会访问一次内存。
Data_t acc = IDENT;
For(i=0; i<length;i++){
acc = acc OP data[i];
}
*dest = acc;
改为这样以后,局部变量可以分配到寄存器中,这样整个循环访问内存的*dest就变为了访问寄存器中的acc,速度理论上变快了。
4)循环展开;首先它减少了不直接有助于程序结果的操作数量,如循环索引计算和条件分支。其次,它提供了一些方法可以进一步变换代码减少整个计算中关键路径上的操作数量。
acc = (acc OP data[i])OPdata[i+1];
5)提高并行性;在循环展开中acc = (acc OPdata[i])OPdata[i+1];的执行一直要用到acc,而指令流水线的存在意味着每个时钟周期都能开始一个新操作。上述代码无疑增加了数据相关性,影响流水线的效率。
acc1 = (acc1 OP data[i])
acc2 = (acc2 OP data[i])
这样就部分解决了数据相关性问题。
到底应该几路并行的问题就和运算单元的延迟相关了。
发射时间:两个连续的同类型运算之间需要的最小时钟周期数。
延迟:完成该运算所需要的总时间。
3程序剖析工具
Gprof
第6章
存储器层次结构,不得不说这是个相当聪明的系统设计方式。程序的局部性原理保证了存储器系统的可行性。不知道你能从这套系统中想到些什么….
这章中的大部分内容在汤子赢的计算机操作系统中都讲了,并且汤的讲的更易懂,更全面。如存储器的一些基础知识,cashe的置换策略,等等.
1磁盘控制器;所有对磁盘的访问工作最终都是通过它实现的。在orange中已经看到,硬盘驱动程序就是对怎么使用磁盘控制器的一种包装。文件系统中所有的操作都是基于硬盘驱动程序的。
它维护了48的位的逻辑号到实际物理扇区的映射关系。即将逻辑号翻译为(盘面,磁道,扇区)三元组。注意这里的逻辑号和虚拟地址是完全不同的两个概念,他们没有一点关系。
这里的逻辑号主要在文件系统这个层次运用。
2固态硬盘是一种基于闪存的存储技术。在大约进行100 000次重复写后,块就会损坏。
3缓存对程序性能的影响。
建议做一下2010年的计算机统考真题的第44题。
意识到缓存的存在,可以让你的程序变快。
4存储器山。这张图就是该书的封面。X轴为数据量大小,y轴为步长,z轴为读吞吐量。