📜  编译器设计-代码优化

📅  最后修改于: 2021-01-18 05:31:19             🧑  作者: Mango


优化是一种程序转换技术,旨在通过使代码消耗更少的资源(即CPU,内存)并提高速度来改进代码。

在优化中,高级通用编程结构被非常有效的低级编程代码取代。代码优化过程必须遵循以下三个规则:

  • 输出代码不得以任何方式改变程序的含义。

  • 优化应该提高程序的速度,并且如果可能的话,程序应该需要更少的资源。

  • 优化本身应该是快速的,并且不应延迟整个编译过程。

可以在编译过程的各个级别上为优化代码做出努力。

  • 开始时,用户可以更改/重新排列代码或使用更好的算法来编写代码。

  • 生成中间代码后,编译器可以通过地址计算和改进循环来修改中间代码。

  • 在生成目标机器代码时,编译器可以利用内存层次结构和CPU寄存器。

优化可以大致分为两类:与机器无关和与机器有关。

与机器无关的优化

在此优化中,编译器将获取中间代码并转换不涉及任何CPU寄存器和/或绝对内存位置的部分代码。例如:

do
{
   item = 10;
   value = value + item; 
} while(value<100);

这段代码涉及到标识符项的重复分配,如果我们这样说的话:

Item = 10;
do
{
   value = value + item; 
} while(value<100);

应该不仅可以节省CPU周期,而且可以在任何处理器上使用。

机器相关的优化

与机器有关的优化是在生成目标代码之后以及根据目标机器体系结构转换代码时进行的。它涉及CPU寄存器,并且可能具有绝对内存引用,而不是相对引用。依赖于机器的优化器会尽最大努力充分利用内存层次结构。

基本块

源代码通常具有许多指令,这些指令始终按顺序执行,并被视为代码的基本块。这些基本块之间没有任何跳转语句,即,当执行第一条指令时,同一基本块中的所有指令将按其出现顺序执行,而不会丢失程序的流控制。

程序可以具有各种构造作为基本块,例如IF-THEN-ELSE,SWITCH-CASE条件语句和循环(例如DO-WHILE,FOR和REPEAT-UNTIL等)。

基本块识别

我们可能使用以下算法在程序中找到基本块:

  • 从所有基本块开始搜索所有基本块的标题语句:

    • 程序的第一条陈述。
    • 任何分支(有条件/无条件)的目标语句。
    • 任何分支语句之后的语句。
  • 标头语句及其后的语句构成一个基本块。

  • 基本块不包含任何其他基本块的任何头声明。

从代码生成和优化的角度来看,基本块都是重要的概念。

基本块

基本块在识别变量中起着重要作用,在单个基本块中多次使用了这些变量。如果任何一个变量被多次使用,则除非该块完成执行,否则不必清空分配给该变量的寄存器存储器。

控制流程图

程序中的基本块可以通过控制流程图来表示。控制流程图描述了如何在块之间传递程序控制。这是一个有用的工具,可通过在程序中找到不需要的循环来帮助优化。

控制流程图

循环优化

大多数程序在系统中循环运行。有必要优化循环以节省CPU周期和内存。可以通过以下技术优化循环:

  • 不变代码:驻留在循环中并在每次迭代中计算相同值的代码片段称为循环不变代码。通过将其保存为仅一次(而不是每次迭代)即可将其移出循环。

  • 归纳分析:如果变量的值在循环内被循环不变值更改,则称为归纳变量。

  • 强度降低:有些表达式消耗更多的CPU周期,时间和内存。这些表达式应替换为更便宜的表达式,而不会影响表达式的输出。例如,就CPU周期而言,乘法(x * 2)比(x << 1)昂贵,并且产生相同的结果。

消除死代码

无效代码是一种或多种代码声明,它们是:

  • 永远不会执行或无法到达
  • 或者,如果执行,则永远不会使用其输出。

因此,死代码在任何程序操作中都不起作用,因此可以简单地将其消除。

部分无效的代码

有些代码语句仅在某些情况下使用其计算值,即有时使用这些值,有时不使用。这种代码称为部分无效代码。

半死代码

上面的控制流程图描述了一个程序块,其中变量“ a”用于分配表达式“ x * y”的输出。让我们假设分配给’a’的值从未在循环内使用过。在控件离开循环后,立即给’a’分配了变量’z’的值,该值将在以后的程序中使用。我们在这里得出结论,赋值代码“ a”从未在任何地方使用过,因此可以消除。

死码

同样,上图描述了条件语句始终为假,这意味着以真实情况编写的代码将永远不会执行,因此可以将其删除。

部分冗余

冗余表达式在并行路径中多次计算,而操作数没有变化。而部分冗余表达式在路径中多次计算,而操作数没有变化。例如,

Redundant Expression

[redundant expression]

Partially Redundant Expression

[partially redundant expression]

循环不变代码是部分冗余的,可以通过使用代码移动技术来消除。

部分冗余代码的另一个示例可以是:

If (condition)
{
   a = y OP z;
}
else
{
   ...
}
c = y OP z;

我们假设操作数( yz )的值没有从变量a分配到变量c改变。在这里,如果条件语句为true,则y OP z计算两次,否则计算一次。可以使用代码移动来消除这种冗余,如下所示:

If (condition)
{
   ...
   tmp = y OP z;
   a = tmp;
   ...
}
else
{
   ...
   tmp = y OP z;
}
c = tmp;

这里,条件是对还是错; y OP z应该只计算一次。