Chapter 5 优化程序性能

编译器的能力和局限性

  • 内存别名使用:两个指针可能指向同一个内存位置
  • 可能出现这种问题,编译器必须进行检查和处理,这限制了可能的优化
  • restrict 关键字,可以告知编译器两个指针不能指向同一块内存,编译器可以进行进一步的优化

内联函数替换(inline substitution)

  • 将函数调用替换成函数体;减轻调用的深度

消除循环中的低效率

  • 比如将复杂的函数加入循环;此时考虑设置局部变量
  • 消除循环中的过程调用;考虑返回值来优化
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];
}
}
  • 上述过程汇编是会发现,每次累积变量的数值都要读入内存再写回内存
    • 解决方案:引入临时变量,该临时变量使用寄存器存储;最后只写入一次内存
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计算后存储的新结果)