<<代码优化:有效使用内存>>代码优化建议:
1. 展开读取内存的循环
2. 消除数据相关性
如果请求的RAM单元存在地址数据相关性(也就是说,一个单元含有另一个单元的地址),那么CPU不能并行地处理它们,而在得到地址之前必须等待。消除数据相关性可以提高指令并发度。
3. 同时向存储控制器发送多个查询
4. 请求按不少于32个字节的增量方式读取数据
在
于内存进行数据交换的过程中所用的最小数据单位至少是32byte,所以处理速度与处理增量成反比。当增量以1(字节、字或双字)读取内存时,由一半的加
载单元永远不会访问。当以增量4(字节、字或双字)读取内存时,仅仅有25%的加载单元会用到,其它的加载单元不会起任何作用。可见,内存中的数据必须尽
可能紧密地存放在一起。
5. 使用所有经历请求的页面
6. 以一种排除了对相同DRAM页面进行选取的增量方式来处理数据
7. 对齐数据源地址
8. 组合执行存取内存的代码
9. 成组进行读写操作
10. 仅仅在必要时才放问内存
11. 从不针对特定平台优化程序。
<<Programming Pearls(Second Edition)>>代码优化规则:
1. 用空间换取时间规则
1.1 扩展数据结构:通常,通过给结构增加其他信息或改变结构内部的信息让它访问的更快能够减少对数据的常用操作所需的时间。
1.2 存储预先计算好的结果:计算函数一次,然后存储计算结果能够减少昂贵函数的重新计算所需要的成本。以后对该函数的请求就只需要通过查表来完成,而不需要重新计算该函数。
1.3 高速缓存:必须降低经常访问的数据的访问成本。
1.4 延迟计算法:除非需要,否则该策略永远不会计算某个元素,这样可以避免计算不必要的元素。
2. 用时间换取空间规则
2.1 压缩:密集存储表示能够通过增加存储和检索所需的时间来降低存储成本。
2.2 解释程序:通常,使用解释程序能够减少表示程序所需的空间,解释程序压缩表示相同的操作序列。
3. 循环规则
3.1 将代码移出循环:最好不要在循环的每次迭代中都执行相同的操作,而是将它放在循环外部,仅仅执行一次。
3.2 合并测试条件:高效的内部循环一个尽量少包含测试条件,最好只有一个。因此,程序员应该尽量使用其他退出条件模拟循环的一些推出条件。哨兵是该规则最常见的应用:在数据结构的边界上放一个哨兵来测试是否已经遍历完整个结构。
3.3 循环展开:展开循环能够消除循环中修改循环下标的成本,同时一能避免管道时延、减少分支,并增加指令级并发。
3.4 传输驱动的循环展开:如果在普通的赋值中,使用了一个成本很高的内部循环,那么通常通过改变所使用的变量能够消除这些赋值。例如:删除赋值i=就,后面的代码必须将j视为i。
3.5 消除无条件分支:在快速的循环中不应该包含无条件分支。通过“旋转”循环,在底部加上一个条件分支,能够消除循环结束处的无条件分支。
3.6 循环合并:如果两个邻近的循环操作作用在同一个元素集上,那么最好合并这两个操作部分,仅仅使用一个循环控制操作。
4. 逻辑规则
4.1 利用袋鼠恒等式:如果逻辑表达式的计算非常昂贵,就使用比较廉价的代数等式表达式来替换。
4.2 简化的单调函数:测试几个部落的单调非递减函数是不是超过了特定的阀值,一旦达到了这个阀值就不需要计算任何变量。该规则的一个更加复杂的应用就是一旦达到了循环的目的就退出循环。
4.3 重新排序测试:在组织逻辑测试的时候,应该将廉价的经常成功的测试放在昂贵的很少成功的测试前面。
4.4 预计算逻辑函数:可以使用表示域的表的查找替代一个小的有限域中的逻辑函数。
4.5 消除布尔变量:我们可以通过用if-else语句替代对布尔变量的v赋值来消除布尔变量,在if-else语句中的一个分支表示v为真的情况,其它的表示v为假的情况。
5. 过程规则
5.1 压缩函数层次:通常,重写函数并绑定过去的变量能够减少调用本身(非递归)函数集合元素的运行时间。
5.2 利用常用情况:一个组织函数正确处理所以情况并高效处理普通情况。
5.3 协同程序:通常,使用协同程序能够将multiple-pass算法转换为single-pass算法。
5.4 递归函数变化:通过下面的转换能够减少递归函数的运行时间:将递归重写为迭代,通过使用栈来将递归转换为迭代。如果函数的最后一步是递归调用自身,那么使用一个到其第一条语句的分支来替换该调用,这通常叫做尾递归。
5.5 并发:在基本的硬件条件下,构建的程序应该能够尽可能地利用并发。
6. 表示规则
6.1 初始化编译时间:在程序执行之前,尽可能初始化变量。
6.2 利用代数恒等式:如果表达式的计算非常昂贵,就应使用开销较小的代数恒等表达式替换它。通常我们可以是向左或向右移位来实现幂的乘或除;尽量减少数组元素上的迭代循环,使用加法替代乘法。
6.3 消除通用子表达式:如果连续两次计算了同一个表达式,并且它的所有变量都没有任何改动,那么就应该避免第二次计算,只需要排序第一次计算结果并将它用于第二次计算中。现在的编译器一般都能消除不包含函数调用的常用子表达式。
6.4 配对计算:如果总是同时计算两个类似的表达式,那么就应该建立一个新的过程并将它们成对计算。
6.5 利用单词并发:充分使用基本计算机体系结构的数据总线宽度来计算昂贵的表达式。