Optimización de código independiente de la máquina en el diseño del compilador

La optimización de código independiente de la máquina intenta hacer que el código intermedio sea más eficiente al transformar una sección de código que no involucra componentes de hardware como registros de CPU o cualquier ubicación de memoria absoluta. Generalmente, optimiza el código eliminando redundancias, reduciendo el número de líneas en el código, eliminando el código inútil o reduciendo la frecuencia del código repetido. Por lo tanto, se puede utilizar en cualquier procesador, independientemente de las especificaciones de la máquina.

La optimización del código independiente de la máquina se puede lograr utilizando los siguientes métodos:

Optimización de conservación de funciones:

Las optimizaciones de preservación de funciones se ocupan del código en una función dada en un intento de reducir el tiempo computacional. Se puede lograr mediante los siguientes métodos:

  1. Eliminación de subexpresiones comunes
  2. Plegable
  3. Eliminación de código muerto
  4. Propagación de copias

1. Eliminación de subexpresiones comunes:

Una subexpresión común es la que se calculó y no cambia después del último cálculo, pero a menudo se repite en el programa. El compilador evalúa su valor incluso si no cambia. Tales evaluaciones resultan en desperdicio de recursos y tiempo. Así es mejor que sea eliminado. Considere un ejemplo:

//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;

Es problemático si una subexpresión común se repite a menudo en un programa. Por lo tanto, debe ser eliminado.

2. Plegado constante:

Constant Folding es una técnica en la que la expresión que se calcula en tiempo de compilación se reemplaza por su valor. Generalmente, tales expresiones se evalúan en tiempo de ejecución, pero si las reemplazamos con sus valores, no necesitan evaluarse en tiempo de ejecución, ahorrando tiempo.

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

//Folding applied
int x=12+c;

El plegado se puede aplicar en booleanos, enteros y números de punto flotante, pero se debe tener cuidado con los números de punto flotante. El plegamiento constante a menudo se intercala con una propagación constante.

Propagación constante:

Si a cualquier variable se le asigna un valor constante y se usa en cálculos posteriores, la propagación constante sugiere usar el valor constante directamente para cálculos posteriores. Considere el siguiente ejemplo

// 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. Eliminación de código muerto:

El código muerto es un fragmento de programa que nunca se ejecuta o nunca se alcanza en un programa. Es un código que se puede eliminar de manera eficiente del programa sin afectar ninguna otra parte del programa. En caso de que se obtenga un valor y nunca se use en el futuro, también se considera código muerto. Considere el siguiente código muerto:

//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);

 Otro ejemplo de código muerto es asignar un valor a una variable y cambiar ese valor justo antes de usarlo. La declaración de asignación de valor anterior es un código muerto. Dicho código muerto debe eliminarse para lograr la optimización.

4. Propagación de copias:

Copy Propagation sugiere utilizar una variable en lugar de otra, en los casos en que se utilicen asignaciones de la forma x=y. Estas asignaciones son declaraciones de copia. Podemos usar y de manera eficiente en todos los lugares requeridos en lugar de asignarlo a x. En resumen, la eliminación de copias en el código es Copy Propagation.

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

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

Otro tipo de optimización, la optimización de bucles, trata de reducir el tiempo que un programa pasa dentro de un bucle.

Optimizaciones de bucle:

El programa pasa la mayor parte de su tiempo dentro de los bucles. Así, los bucles determinan la complejidad temporal del programa. Entonces, para obtener un código óptimo y eficiente, se requiere la optimización del bucle. Para aplicar la optimización de bucle, primero debemos detectar el bucle mediante el análisis de flujo de control con la ayuda del gráfico de flujo del programa. Un ciclo en un gráfico de flujo de programa indicará la presencia de un bucle. Tenga en cuenta que el código de la fase de generación de código intermedio que está en formato de tres direcciones se proporciona como entrada para la fase de optimización. Es difícil identificar bucles en dicho formato, por lo que se requiere un diagrama de flujo del programa.

El gráfico de flujo del programa consta de bloques básicos, que no es más que el código dividido en partes o bloques y muestra el flujo de ejecución del código,

Gráfico de flujo del programa de muestra

El ciclo en el gráfico anterior muestra la presencia de un bucle desde el bloque 2 hasta el bloque 3. 

Una vez que se detectan los bucles, se pueden aplicar las siguientes técnicas de optimización de bucles:

  1. Reducción de frecuencia
  2. Simplificación de expresiones algebraicas
  3. Reducción de fuerza
  4. Eliminación de redundancia

1. Reducción de frecuencia:

Se aplica al concepto de que un ciclo se ejecuta por la menor cantidad posible de líneas de código. Se puede lograr mediante los siguientes métodos:

una. Movimiento de código:

Muchas veces, en un ciclo, las declaraciones que permanecen sin cambios en cada iteración se incluyen en el ciclo. Tales declaraciones son invariantes del ciclo y solo dan como resultado que el programa pase más tiempo dentro del ciclo. El movimiento del código simplemente mueve el código invariable del bucle fuera del bucle, lo que reduce el tiempo que se pasa dentro del bucle. Para entender esto, considere el siguiente ejemplo.

//Before code motion

p=100
for(i=0;i<p;i++)
{
    a=b+40;         //loop invariant code
    if(p/a==0)
        printf("%d",p);
}

// After code motion

p=100
a=b+40;
for(i=0;i<p;i++)
{
    if(p/a==0)
        printf("%d",p);
}

En el ejemplo, antes de optimizar, se evaluó el código invariable del ciclo para cada iteración del ciclo. Una vez que se aplica el movimiento del código, la frecuencia de evaluación del código invariable en bucle también disminuye. Por lo tanto, también se denomina Reducción de frecuencia. El siguiente es también un ejemplo de movimiento de código.

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

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

b. Desenrollado de bucle:

Si un ciclo se ejecuta realizando la misma operación para cada iteración, podemos realizar esa misma operación dentro del ciclo más de una vez. Esto se llama desenrollado de bucle. Dicho ciclo desenrollado realizará la evaluación más de una vez en una única iteración de ciclo.

//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++;
}

Como en el ejemplo anterior, un bucle desenrollado es más eficiente que el bucle anterior.

C. Atasco de bucle:

La combinación de bucles que realizan las mismas operaciones se denomina atasco de bucle.

//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;
        }
    }
----;

Por lo tanto, en lugar de ejecutar los mismos bucles dos veces, esa operación se puede realizar ejecutando el bucle solo una vez.

2. Simplificación de expresiones algebraicas:

Un programa puede contener algunas expresiones algebraicas triviales que no resultan en ningún cálculo útil o cambio de valor. Estas líneas de código se pueden eliminar para que el compilador no pierda tiempo evaluándolas. Por ejemplo,

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

Estas declaraciones no dan como resultado ningún cálculo útil. Dicho código puede parecer inofensivo, pero cuando se usa dentro de cualquier ciclo, el compilador continúa evaluándolo. Por lo tanto, lo mejor es eliminarlos.

3. Reducción de fuerza:

Sugiere reemplazar una operación costosa como la multiplicación por una más barata. 

Example:
a*4 

after reduction
a<<2

Es una optimización importante para programas en los que los accesos a arrays ocurren dentro de bucles y debe usarse solo con operandos enteros.

4. Eliminación de redundancia:

Puede ocurrir que una determinada expresión se repita en un código muchas veces. Esta expresión es redundante para el código porque podemos evaluarla una vez y sustituir su próxima ocurrencia con su valor evaluado. Esta sustitución no es más que eliminación de redundancia. Un ejemplo simple se da a continuación

//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

La eliminación de redundancia evita evaluar las mismas expresiones varias veces, lo que da como resultado una ejecución más rápida.

Publicación traducida automáticamente

Artículo escrito por thombreankita y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *