📜  编译器设计中的机器无关代码优化

📅  最后修改于: 2022-05-13 01:57:02.643000             🧑  作者: Mango

编译器设计中的机器无关代码优化

机器无关代码优化试图通过转换一段不涉及硬件组件(如 CPU 寄存器或任何绝对内存位置)的代码来提高中间代码的效率。一般来说,它通过消除冗余、减少代码行数、消除无用代码或减少重复代码的频率来优化代码。因此,无论机器规格如何,都可以在任何处理器上使用。

可以使用以下方法实现与机器无关的代码优化:

函数保持优化:

函数保留优化处理给定函数中的代码,以尝试减少计算时间。可以通过以下方法实现:

  1. 公共子表达式消除
  2. 折叠式的
  3. 死码消除
  4. 复制传播

1.公共子表达式消除:

公共子表达式是计算出来的,在最后一次计算后不会改变,但在程序中经常重复。即使它没有改变,编译器也会评估它的值。这样的评估导致资源和时间的浪费。因此最好将其消除。考虑一个例子:

//Code snippet in three address code format
t1=x*z;
t2=a+b;
t3=p%t2;
t4=x*z;     //t4 and t1 is same expression 
            //but evaluated twice by compiler.
t5=a-z;

// after Optimization
t1=x*z;
t2=a+b;
t3=p%t2;
t5=a-z;

如果一个公共子表达式在程序中经常重复出现,那就麻烦了。因此,它需要被淘汰。

2. 恒定折叠:

常量折叠是一种在编译时计算的表达式被其值替换的技术。通常,此类表达式在运行时进行评估,但如果我们将它们替换为它们的值,则无需在运行时进行评估,从而节省时间。

//Code segment
int x= 5+7+c;

//Folding applied
int x=12+c;

折叠可以应用于布尔、整数以及浮点数,但应该小心浮点数。恒定折叠通常与恒定传播交错。

不断传播:

如果任何变量被分配了一个常数值并用于进一步的计算,常数传播建议直接使用该常数值进行进一步的计算。考虑下面的例子

// Code segment
int a = 5;
int c = b * 2;
int z = a;

//Applying constant propagation once
int c = 5 * 2;
int z = a;

//Applying constant propagation second time
int c = 10;  
int z = a;

//Applying constant propagation last time
int z = a[10];

3. 死码消除:

死代码是在程序中从未执行或从未到达的程序片段。它是可以有效地从程序中删除而不影响程序的任何其他部分的代码。万一获得了一个值并且以后不再使用,它也被视为死代码。考虑下面的死代码:

//Code
int x= a+23;  //the variable x is never used 
              //in the program. Thus it is a dead code.
z=a+y;
printf("%d,%d".z,y);

//After Optimization
z=a+y;
printf("%d,%d".z,y);

死代码的另一个例子是为变量赋值并在使用它之前更改该值。前面的赋值语句是死代码。这样的死代码需要删除才能实现优化。

4.复制传播:

在使用 x=y 形式的赋值的情况下,复制传播建议使用一个变量而不是其他变量。这些分配是复制语句。我们可以在所有需要的地方有效地使用 y,而不是将其分配给 x。简而言之,消除代码中的副本就是复制传播。

//Code segment
----;
a=b;
z=a+x;
x=z-b;
----;

//After Optimization
----;
z=b+x;
x=z-b;
----;

另一种优化,循环优化处理减少程序在循环中花费的时间。

循环优化:

程序大部分时间都在循环中。因此,循环决定了程序的时间复杂度。因此,为了获得最优且高效的代码,需要循环优化。为了应用循环优化,我们首先需要借助程序流程图使用控制流分析来检测循环。程序流程图中的循环将指示循环的存在。请注意,来自中间代码生成阶段的三地址格式的代码作为优化阶段的输入。这种格式的循环很难识别,因此需要程序流程图。

程序流程图由基本块组成,只不过是将代码分成部分或块并显示代码的执行流程,

示例程序流程图

上图中的循环表明存在从块 2 到块 3 的循环。

一旦检测到循环,就可以应用以下循环优化技术:

  1. 频率降低
  2. 代数表达式简化
  3. 强度降低
  4. 冗余消除

1. 降频:

它适用于循环运行尽可能少的代码行的概念。可以通过以下方法实现:

一种。代码运动:

很多时候,在循环中,每次迭代都保持不变的语句包含在循环中。这样的语句是循环不变量,只会导致程序在循环中花费更多时间。代码运动只是将循环不变的代码移到循环外,从而减少在循环内花费的时间。要理解这一点,请考虑以下示例。

//Before code motion

p=100
for(i=0;i

在该示例中,在优化之前,循环不变的代码会针对循环的每次迭代进行评估。一旦应用了代码运动,评估循环不变代码的频率也会降低。因此也称为降频。以下也是代码运动的示例。

//Before code motion
----;
while((x+y)>n)
{
    ----;
}
----;

// After code motion
----;
int t=x+y;
while(t>n)
{
    ----;
}
----;

湾。循环展开:

如果一个循环对每次迭代都执行相同的操作,我们可以在循环内多次执行相同的操作。这称为循环展开。这种展开的循环将在单个循环迭代中多次执行评估。

//Before Loop unrolling

while(i<50)       //while loop initialize all array elements to 0; 
                 //one element each iteration. Thus the loop runs 50 times.
{
    x[i]=0;
    i++;
}

//After loop unrolling

while(i<50)       //After unrolling, each iteration 
                  //initializes 5 elements to 0; 
                  //Thus this loop runs only 5 times.
{
    x[i]=0;
    i++;
    x[i]=0;
    i++;
    x[i]=0;
    i++;
    x[i]=0;
    i++;
    x[i]=0;
    i++;
}

如上例所示,展开的循环比前一个循环更有效。

C。环路干扰:

组合执行相同操作的循环称为循环干扰。

//Before loop jamming
----;
    for(i=0;i<5;i++)      //Setting all elements of 2D array to 0.
    {
        for(j=0;j<5;j++)
        {
            x[i][j]=0;

        }
    }
    for(i=0;i<5;i++)     //Setting diagonal elements to 1.
    {
        x[i][i]=0;


    }
----;
   
//After loop jamming
----;
    for(i=0;i<5;i++)      //Setting all elements of 2D array to 0 
                          //and diagonal elements to 1.
    {
        for(j=0;j<5;j++)
        {
            x[i][j]=0;
            x[i][i]=1;
        }
    }
----;

因此,可以通过只执行一次循环来完成该操作,而不是执行两次相同的循环。

2. 代数表达式化简:

一个程序可能包含一些琐碎的代数表达式,它们不会导致任何有用的计算或值的变化。可以消除这样的代码行,这样编译器就不会浪费时间评估它。例如,

A=A+0;
x=x*1;

这些语句不会导致任何有用的计算。这样的代码可能看起来无害,但是当在任何循环中使用时,它们会继续被编译器评估。因此最好消除它们。

3.强度降低:

它建议用更便宜的操作代替像乘法这样昂贵的操作。

Example:
a*4 

after reduction
a<<2

对于在循环中发生数组访问并且只能与整数操作数一起使用的程序,这是一项重要的优化。

4.冗余消除:

可能会发生特定表达式在代码中重复多次的情况。这个表达式对于代码来说是多余的,因为我们可以评估它一次并用它的评估值替换它的下一次出现。这种替代只不过是消除冗余。下面给出一个简单的例子

//Code:
----;
int x=a+b;
----;
int z=a+b+40;
----;
return (a+b)*5;

//After optimization
----;
int x=a+b;
----;
int z=x+40;
----;
return x*5

冗余消除避免了多次评估相同的表达式,从而加快执行速度。