Chapter 5 优化程序性能¶
约 694 个字 22 行代码 4 张图片 预计阅读时间 4 分钟
编译器的能力和局限性¶
- 内存别名使用:两个指针可能指向同一个内存位置
- 可能出现这种问题,编译器必须进行检查和处理,这限制了可能的优化
- restrict 关键字,可以告知编译器两个指针不能指向同一块内存,编译器可以进行进一步的优化
内联函数替换(inline substitution)¶
- 将函数调用替换成函数体;减轻调用的深度
消除循环中的低效率¶
- 比如将复杂的函数加入循环;此时考虑设置局部变量
- 消除循环中的过程调用;考虑返回值来优化
C
void combine3(vec_ptr v, data_t* dest) {
long i;
long length = vec_length(v);
// 消除过程调用
data_t* data = get_vec_start(v);
*dest = IDENT;
for(int i = 0; i < length; ++i) {
*dest = *dest OP data[i];
}
}
- 上述过程汇编是会发现,每次累积变量的数值都要读入内存再写回内存
- 解决方案:引入临时变量,该临时变量使用寄存器存储;最后只写入一次内存
C
void combine3(vec_ptr v, data_t* dest) {
long i;
long length = vec_length(v);
// 消除过程调用
data_t* data = get_vec_start(v);
data_t acc = IDENT;
for(int i = 0; i < length; ++i) {
acc = acc OP data[i];
}
*dest = acc;
}
处理器操作的抽象模型¶
数据流图¶
- 可以把循环寄存器单独拿出来;根据不同循环操作的周期可以大概估计出该程序性能瓶颈位置
循环展开¶
- 通过增加每次迭代计算的数量;减少循环的迭代次数
- 但是循环展开可能优化程度有限;取决于关键路径的执行过程
提高并行性¶
- 整数运算可以;但是浮点数的加法和乘法不能结合;由于浮点数的舍入和溢出可能造成不同的结果
- 重新结合变换,将括号的位置改变;可能可以继续优化程序的性能
循环展开和并行积累在多个值中,是提高程序性能的更可靠方法
- SIMD:单指令流多数据流方式执行程序;每次运算执行向量的计算
限制因素¶
- 寄存器溢出:如果溢出,将两个数相乘直接保存在寄存器的优势消失
- 分支预测和预测错误惩罚:惩罚是19个时钟周期
- 循环分支被预测为选择分支,只在最后一次导致预测错误惩罚
- 书写适合用条件传送实现的代码:三元操作符代替if-else
内存性能¶
- 加载的性能:除开使用cache,每次进行取取操作数,都需要访存
-
存储的性能:每次存放结果,也需要访存
-
注意程序中可能存在潜在的加载-存储相关的操作(src和dest指向相同的地址或者dest依赖于src计算后存储的新结果)