A menudo se puede lograr que el código directamente producido por los algoritmos de compilación se ejecute más rápidamente o que ocupe menos espacio, o ambas cosas. Esta mejora se consigue mediante transformaciones de programas que tradicionalmente se denominan “optimaciones”.
Los compiladores que aplican transformaciones para mejorar el código se denominan compiladores optimizadores.
En la práctica, los lazos internos del programa son buenos candidatos para realizar mejoras.
Atributos de Calidad del Código Generado
El código generado por un compilador tiene dos atributos que determinan la calidad del mismo:
- Tamaño
- Tiempo requerido para su ejecución
Normalmente, el código generado tiene carencias en cuanto a estos atributos, es por ello que se añade otra fase al compilador denominada optimización.
Criterios para las Transformaciones de Mejora del Código
Las mejores transformaciones de programas son las que producen el mayor beneficio con el menor esfuerzo. Las transformaciones realizadas por un compilador optimizador deben tener varias propiedades:
- Una transformación debe preservar el significado de los programas.
- Una transformación debe, como promedio, acelerar los programas en una cantidad mensurable.
- Una transformación debe valer la pena.
Tipos de Optimización
Optimización mediante “Mirilla”
Una técnica sencilla pero efectiva para mejorar localmente el código objeto es la optimización mediante “mirilla”, un método para intentar mejorar el rendimiento del proyecto objeto examinando una secuencia corta de instrucciones objeto y sustituyendo estas instrucciones por una secuencia más corta o más rápida, si es posible.
Una característica de la optimización mediante mirilla es que cada mejora puede brindar oportunidades para mejoras adicionales. En general, son necesarias repetidas pasadas sobre el código objeto para obtener las mayores ventajas.
Optimización mediante “Mirilla”: Cargas y Almacenamientos Redundantes
(1) MOV R0, a
(2) MOV a, R0
Se puede borrar la instrucción MOV a, R0 porque siempre que se ejecute (2), (1) garantizará que el valor de a ya está en el registro R0.
Optimización mediante “Mirilla”: Optimización del Flujo de Control
A menudo los algoritmos para generación de código intermedio producen saltos hacia saltos, saltos hacia saltos condicionales, o saltos condicionales hacia saltos. Estos saltos innecesarios se pueden eliminar, se puede sustituir la secuencia de saltos:
goto L1. goto L2
… por la secuencia …
L1: goto L2 L1: goto L2
Si ahora no hay saltos a L1, entonces se puede eliminar la proposición L1: goto L2, siempre que vaya precedida de un salto incondicional
Optimización del Flujo de Control
De forma similar la secuencia:
if a < b goto L1 -> L1 go to L2
if a < b goto L2 -> L1: goto L2
Optimización mediante “Mirilla”: Simplificación Algebraica
No hay límite del número de simplificaciones algebraicas que se pueden intentar con la optimización mediante mirilla. Sin embargo, sólo unas pocas identidades algebraicas ocurren con la frecuencia suficiente como para que valga la pena considerar su implantación. Por ejemplo, las proposiciones como:
x := x + 0 ó x := x * 1
son producidas por algoritmos directos para la generación de código intermedio, y se pueden eliminar fácilmente con la optimización mediante mirilla.
Optimización Local y Global
Si la transformación de un programa se realiza observando solamente proposiciones de un bloque básico, esta se denominará optimización local; si se analiza el programa en conjunto, estudiando bloques, estructuras, ciclos, etc. localizados dentro de cualquier parte del programa, se estaría hablando de una optimización global.
Costos de Optimización
Los costos son el factor más importante para tomar en cuenta al momento de optimizar, ya que en ocasiones la mejora obtenida puede no reflejarse en el producto final, pero sí ser perjudicial para el equipo de desarrollo.
Costos de Ejecución
Son aquellos que van implícitos al ejecutar un programa; los programas tienen un costo mínimo para su ejecución, por lo que el espacio y la velocidad del procesador son tan solo algunos elementos a considerar al momento de optimizar.
Herramientas para el Análisis de Flujo de Datos
Un compilador es un tipo especial de traductor en el que el lenguaje fuente es un lenguaje de alto nivel y el lenguaje objeto es de bajo nivel.
Depurador
- Herramienta que permite a los programadores encontrar y corregir errores en el código.
- Permite a los desarrolladores ejecutar su programa paso a paso y examinar el estado de las variables y los valores en tiempo de ejecución.
- Proporciona una visión detallada de lo que está sucediendo en el programa, lo que facilita la identificación de errores y la corrección de estos.
Lenguaje Ensamblador
- Las instrucciones se expresan empleando códigos mnemotécnicos en vez de números binarios, haciendo más legibles y más fáciles de manipular los programas.
- Los lenguajes ensambladores siguen estando demasiado cercanos a la máquina, por lo que son difícilmente portables, puesto que las instrucciones, modos de direccionamiento y operandos empleados por dos máquinas distintas difieren entre sí.
- Este lenguaje sigue estando demasiado alejado del lenguaje humano y de su forma de razonar y de expresarse, por lo que sigue siendo de difícil manipulación.
- Escribir un programa en lenguaje ensamblador requiere conocimientos acerca del hardware (arquitectura) de la computadora, su conjunto de instrucciones y reglas de uso.
- Un segmento es un área especial en un programa que inicia en un límite de un párrafo, los tres segmentos principales son los segmentos de:
- Código (CS).
- Datos (DS).
- Pila (SS).
- Todos aquellos problemas a los que se pretende dar una solución informática se plantean en el ámbito de expresión de algún lenguaje natural, y para que dicha solución (mediante ordenes o instrucciones) pueda ser entendida por un ordenador, debe ser traducida a un lenguaje denominado lenguaje máquina.
- Se caracteriza por:
- Ser el único lenguaje inteligible directamente por un ordenador.
- Basarse en la combinación de dos únicos símbolos, el cero y el uno, denominados bit.
Administración de Memoria
- La correspondencia entre los nombres del programa fuente con direcciones de objetos de datos en la memoria durante la ejecución se realiza en cooperación con el generador de código. Las entradas en la Tabla de Símbolos se van creando conforme se examinan las declaraciones de un procedimiento.
- El tipo en una declaración determina la cantidad de memoria necesaria para el nombre declarado.
- Según la información de la Tabla de Símbolos se pueden determinar una dirección relativa para el nombre de un área de datos para el procedimiento.
Suponiendo que el compilador obtiene un bloque de memoria del sistema operativo para que se ejecute el programa compilado, para el momento de ejecución esta debe estar subdividida de modo que pueda albergar:
- El código objeto generado.
- Los objetos y los datos.
- Una contrapartida de la pila de control para registrar las activaciones de procedimientos.