优化程序性能
这一章基本用总结写得相对精简,但是浓缩的是精华。
第一 ,用合适的算法和数据结构。
第二,编写出编译器能够有效优化以转换成高效可执行的代码。
有两个概念,时间局部性,空间局部性。
5.1 优化编译器的能力和局限性
编辑器会默认指针有可能是指向的同一个位置,所以他们不会对一类指针进行优化编译。 这种叫内存别名使用,就是默认两个指针指向的是同一个位置,我就不优化了,需要我们去代码甄别优化。
全局变量的影响,全局变量的一些低级代码行为,编辑器也不会优化。
5.2 表示程序性能
CPE 每元素的周期数
说的就是程序消耗了多少机器的时钟周期。
5.3程序示例
有一些明显没有必要的边界检查,消耗了性能。
5.4消除循环的低效率
那一个程序耗时基本都是在循环里面
我们可以代码移动,优化在循环里面多次求值,但是这个值在循环中并不会被改变,那么可以将这个求值放在循环外。
5.6消除不必要的内存引用
for{
*dest = *dest OP data[i]
}
变成
data_t acc = IDENT
for{
acc = acc OP data[i];
}
也就是 *dest 变成了acc 取消了dest的内存引用不需要取地址 acc是存放在寄存器,没有取地址这一个操作。 后面讲内存 高级缓存 CPU如何获取地址值就清晰一些。
5.7 理解现代处理器
如果一系列操作必须按照严格的顺序执行的时候就会遇到延迟界限。
因为在下一条指令开始时,需要用到上一条指令的值,那么上一条指令必须要先完成,这限制了程序的性能。
5.7.1整体操作
ICU 指令控制单元。 从指令高速缓存 中读取指令,
EU 执行单元。
一般计算机有多个功能单元
0 整数运算 浮点数加减乘除 分支
1 整数运算 浮点数加减乘除
2 .3 加载计算地址
4 存储
5 存储计算地址 ....
退役单元;指令退役时候 程序寄存器才会被更新。
5.7.2 功能单元的性能
延迟: 这个指令的耗时
发射时间:这个指令完全结束到下个指令开始之间的时间差
容量: 能够执行该功能单元的数量
加法1 乘法3 除法3~30
5.7.3 处理器操作的抽象模型
讲了一下 什么是关键路径: 也就是每次循环最耗时的部分,一般都是计算迭代值最耗时的那条线。
这条线主要影响了程序性能。
举个例子: 一个循环有乘法除法加法同时进行,除法耗时8个tick 乘法 3个tick 加法1.5个tick 那么除法计算的那条线就是关键路径。
5.8循环展开
for(i++) 变成for(i+2) 里面计算就是一次性计算两次。
5.9提高并行性
for 循环拆分为两条线就相当于是并行计算了,循环展开还是一条线,然后在用两条线的和 计算总值。
重新结合变换:就是减少迭代值得计算,可以优先结合迭代值和迭代值算数运算,在计算常量和迭代值得算数运算。
5.11 一些限制因素
寄存器溢出
并行度不能操作寄存器数量。 操作了参数就存放在内存中,取出非常耗时。
5.11.2 分支预测和预测错误处罚
前面说了计算机有一种策略,会提前预判一下下个分支是什么,预判成功皆大欢喜,预判错误,重新回到判断点 ,这有一个预测错误得惩罚时间,就需要利用条件转移得方式来写代码,上面提到过。
5.12.2 存储的性能
说了一下当短时间取值和存取都是同一个地址的时候,会有一个很大的时耗,避免这种操作。
所以很多代码进来就搞一个数组,然后memcpy。
5.13 性能提高技术
1.高级设计,为遇到的问题选择适当的算法和数据结构
2.基本编码原则。避免限制优化的因素。
消除连续的函数调用
消除不必要的内存引用
3.低级优化 结构化代码以利用硬件功能
展开循环
多个累计变量重新结合
5.14 确认和消除性能瓶颈
gprof prog 可以计算函数用时
说了一下常见的性能刨析程序