CSAPP Optimize 笔记

5.1 编译器优化能力的局限性

  • 编译器只做安全的优化–优化后和未优化的版本有一样的行为

限制

  • 内存别名使用

编译器并不知道指针指向哪里,因此它必须假设指针可能指向同一个位置.

  • 函数调用

大多数编译器不会去判断一个函数是否有副作用,因此它们倾向于将函数的调用保持不变

内联

包含函数调用的代码可以用内联函数替换过程进行优化,就是将函数内部的执行步骤内联到一起

在gcc中,只尝试单文件的内联,不会尝试多文件的内联(比如一组函数在其他文件内的函数中被调用)

5.2 表示程序性能

标准:CPE

每元素的周期数

5.3 消除低效循环

例如 :可以拿出来的拿出来

5.4减少过程调用

例: 如果一个循环中不断从结构体中的列表中获取值,而同时,结构体会不断对列表中值的存在进行判断

则可在保证安全的情况下,直接获取结构体中的列表进行访问.

5.5消除不必要的内存引用

例如

1
2
3
4
5
6
7
8
9
{// for loop
*dest=*dest+1;
}
// 将dest这个不断访问内存的东西挪到寄存器里
val tmp=*dest;
{
tmp=tmp+1;
}
*dest=tmp

5.6 了解现代处理器

  • 延迟界限

    当一系列操作必须按照严格顺序执行时会碰到

    因为下一条指令开始前,这一条必须结束

    代码中的数据相关限制了处理器利用指令级并行的能力时会碰到延迟界限

  • 吞吐量界限

    是处理器功能单元的原始计算能力,是程序性能的终极限制

5.7 循环展开

  • 比如n长的循环,2个一组展开之后,循环长度/2,这种称之为1*2循环展开

对一个n长的循环,进行k级别的展开,就需要将上限设置为n-k+1,这样最大循环索引会小于n

5.8 提高并行性

5.8.1 多个累积变量

例: 见书P370

上面的例子称之为2*2循环展开

5.8.2重新结合变换

该例子为2*1a循环展开

// slow version

acc= (acc * data[i]) * data[i+1]

//fast version

acc = acc * ( data[i] * data[i+1])

slow版里,每次计算都需要等之前的计算结果出来之后才能继续进行

fast版里,data[i]*& data[i+1]不受约束,因此可以被CPU并行优化,故效率高

5.9 一些限制因素

书P378

5.9.1 寄存器溢出

当用到的临时变量过多,使得寄存器不够用的时候,会调用栈来存储这些变量,这会使得程序效率变低.

5.9.2 分支预测和预测错误处罚

预测错误会导致较大的错误处罚,那么有什么办法来保证这个处罚对程序效率影响较小呢

  1. 不过分关心可预测的分支

    例如,大部分结束循环的语句判断都是不结束,预测时一般都按照不结束来判断. 这时候只在最后一次会导致预测错误处罚.

  2. 书写适合使用条件传送实现的代码

    最好使用条件数据传送而非条件控制转移.

    数据传送示例:

    max=a>b?a:b;

    这种形式适合流水线并行操作.

5.10 应用 性能提高技术

  1. 高级设计: 为遇到的问题选择适当的算法和数据结构
  2. 基本编码原则. 避免限制优化的因素
    1. 消除连续的函数调用
      • 在可能时,将计算一道循环外
    2. 消除不必要的内存引用
      • 引入临时变量来保存中间结果,只有在最后的值计算出来时,才将结果存放到数组和全局变量中.
  3. 低级优化. 结构化代码以利用硬件性能
    1. 展开循环,降低开销
    2. 使用多个累计变量和重新结合技术,找到方法提高指令级并行
    3. 用功能性的风格重写条件操作,使编译采用条件数据传送.
Author

YSH

Posted on

2022-10-08

Updated on

2022-10-13

Licensed under