martes, 22 de octubre de 2019

3.1.4 De mirilla


De mirilla 

La optimización de mirilla trata de estructurar de manera eficiente el flujo del programa, sobre todo en instrucciones de bifurcación como son las decisiones, ciclos y saltos de rutinas.
 La idea es tener los saltos lo más cerca de las llamadas, siendo el salto lo más pequeño posible.
Instrucciones de bifurcación
 Interrumpen el flujo normal de un programa, es decir que evitan que se ejecute alguna instrucción del programa y salta a otra parte del programa.
Por ejemplo: el “break”
        Switch (expresión que estamos evaluando)
{
Case 1: cout << “Hola” ;
 Break;
Case 2: cout << “amigos”;
Break;
}

3.1.3 Globales.


Globales.  
La optimización global se da con respecto a todo el código. Este tipo de optimización es más lenta pero mejora el desempeño general de todo programa. Las optimizaciones globales pueden depender de la arquitectura de la máquina. 

En algunos casos es mejor mantener variables globales para agilizar los procesos (el proceso de declarar variables y eliminarlas toma su tiempo) pero consume más memoria. Algunas optimizaciones incluyen utilizar como variables registros del CPU, utilizar instrucciones en ensamblador.

Como puntos mas resultantes son:

• La optimización global se da con respecto a todo el código.
• Este tipo de optimización es más lenta pero mejora el desempeño general de todo programa.
• Las optimizaciones globales pueden depender de la arquitectura de la máquina.

3.1.2 Ciclos

3.1.2 Ciclos
Factorización de expresiones invariantes
Expresiones invariantes de bucle: expresiones cuyo valor es constante durante toda la
ejecución del bucle


incuyen constantes y/o variables no modi_cadas en el cuerpo del bucle

Principio: Mover expresiones invariantes desde el cuerpo hasta la cabeza del bucle
-> al sacarlas del bucle, pueden quedar dentro de otro bucle externo)
-> repetir proceso.
Ejemplos:
while (i

Algunas optimizaciones son difíciles de implementar
Algunas optimizaciones son costosas en términos de tiempo de compilación
La optimización más elaborada es difícil y costosa

Meta: Mejora Máxima con costo mínimo

Los costos son el factor más importante a tomar en cuenta a la hora de optimizar ya que
en ocasiones la mejora obtenida puede verse no reflejada en el programa final pero si
ser perjudicial para el equipo de desarrollo.
La optimización de una pequeña mejora tal vez tenga una pequeña
ganancia en tiempo
o en espacio pero sale muy costosa en tiempo en generarla.
Pero en cambio si esa optimización se hace por ejemplo en un ciclo, la mejora obtenida
puede ser N veces mayor por lo cual el costo se minimiza y es benéfico la mejora.
Por ejemplo: for(int i=0; i < 10000; i++); si la ganancia es de 30 ms 300s

3.1.1 Locales.


3.1.1 Locales
Reacondicionamiento de operadores
Cambiar orden de evaluación aplicando propiedades conmutativa, asociativa y
distributiva.
V:= W * X *( Y + Z )
=
V := ( Y + Z ) * W * X
Código no optimizado, con 7 líneas de código.
1. MOV AX, W
2. MUL AX, X
3. MOV t1, AX
4. MOV AX, Y
5. ADD AX, Z
6. MUL AX, t1
7. MOV V, AX
Código optimizado a solo 5 líneas de código.
1.
2.
3.
4.
5.

MOV AX, Y
ADD AX, Z
MUL AX, W
MUL AX, X
MOV V, AX

lunes, 21 de octubre de 2019

Programa Infija a Prefija






3.1-Tipos de Optimización.



                                       3.1 Tipos de optimización.

En ciencias de la computación, un compilador optimizador es un compilador que trata de minimizar ciertos atributos de un programa informático con el fin de aumentar la eficiencia y rendimiento.Las optimizaciones del compilador se aplican generalmente mediante una secuencia de transformaciones de optimización, algoritmos que transforman un programa para producir otro con una salida semánticamente equivalente pero optimizado.

Generalmente hay varios aspectos que se desean optimizar:


  1. Optimización temporal: Reducir el tiempo de ejecución del programa.
  2. Optimización espacial: Reducir la cantidad de espacio en memoria que ocupa el programa en ejecución.
  3. Reducir el tamaño del programa.
  4. Minimizar la potencia consumida por un programa (debido a las computadoras portátiles).

Etapas de compilación

La optimización se realiza después de la generación de código de todo el programa o de un elemento ejecutable del programa (funciónprocedimiento, etc), por ende es dependiente del contexto.La condición que ha de cumplir es que el código optimizado se ha de comportar igual que el código de partida, excepto por ser más rápido o ocupar menos espacio.
Se ha demostrado que algunos problemas de optimización de código son NP-completo, o incluso indecidibles.​ En la práctica, factores como la voluntad del programador que debe esperar a que el compilador complete sus tareas, imponen límites superiores en las optimizaciones que las que una simple implementación del compilador puede proporcionar (la optimización es un proceso muy intensivo que generalmente necesita mucho procesamiento y memoria para llevarse a cabo). En el pasado, las limitaciones de memoria de las computadoras también eran un factor importante en la limitación de las optimizaciones que se podían realizar. Debido a todos estos factores, la optimización rara vez produce una salida de forma óptima (valga la redundancia), y el hecho de que una optimización pueda impedir el rendimiento en algunos casos, hace que, sean métodos heurísticos para mejorar el uso de los recursos en los programas típicos.

La optimización es el campo donde se hace más investigación en los compiladores hoy en día. Las tareas del front-end (exploración, análisis sintácticoanálisis léxicoanálisis semántico) son bien conocidas y, sin optimizar, la generación de código es relativamente sencilla. La optimización, por otra parte, aún es un campo que no se ha terminado de desarrollar de forma completa.


domingo, 22 de septiembre de 2019

Notación Polaca

Notación Polaca


notación polaca, también conocida como notación de prefijo o notación prefija, es una forma de notación para la lógica, la aritmética, el álgebra y la computación. Su característica distintiva es que coloca los operadores a la izquierda de sus operandos.


La notación polaca, también conocida como notación prefija, es un tipo de notación que se aplica en lógica, aritmética y álgebra. Fue creada por Jan Łukasiewicz allá por los años 20 del siglo XX. Su principal característica, y por lo que se la conoce como notación prefija, es que los operadores preceden a los operandos, lo que significa, traducido al ámbito de la lógica, que las conectivas preceden a las variables.
El alcance de un operador queda perfectamente determinado por su posición en la fórmula, cuanto más a la izquierda se haye un operador mayor alcance tiene. Del mismo modo, la conectiva principal de una fórmula en notación polaca es la situada más a la izquierda.
Este tipo de notación evita por tanto el uso de símbolos auxiliares (paréntesis, corchetes…) para establecer el alcance de las conectivas, a diferencia de la notación estándar. Esta notación puede leerse sin ambigüedad sin recurrir a estos símbolos. Esta característica hace su escritura más compacta.
Por ejemplo, el axioma de no contradicción, que en notación estándar escribimos ¬(p ∧ ¬ p), en notación polaca lo expresaríamos así: NKpNp.

Conectivas en notación polaca

Tradicionalmente, la notación polaca usa letras mayúsculas para expresar conectivas, tomando normalmente la primera letra del nombre de la conectiva en polaco. Aquí tenemos una lista con los símbolos más comunes:

Definición de fórmula bien formada en notación polaca

Tomando la lista anterior de conectivas en notación polaca más un conjunto de variables proposicionales como alfabeto de un lenguaje, tenemos la siguiente definición recursiva de fórmula bien formada en dicho lenguaje:
  • Sea α una variable proposicional. α es una fórmula bien formada.
  • Sean αβ fórmulas bien formadas. CαβKαβAαβEαβΠαΣα son fórmulas bien formadas.

Método para traducir una fórmula en notación estándar a notación polaca

(1) Buscamos la conectiva principal y escribimos la equivalente en notación polaca.
(2) Si la conectiva es unaria, tomamos su argumento como una subfórmula y aplicamos (1).
(3) Si la conectiva es binaria, (3a) tomamos el primer argumento y aplicamos (1), a continuación tomamos el segundo argumento y aplicamos (1).
(4) Si el símbolo es una variable proposicional, la escribimos a continuación.
El procedimiento acaba en un número finito de pasos dado que en sucesivas aplicaciones de (1) la complejidad de la fórmula disminuye. Si se trata de una fórmula bien formada, el procedimiento acabará por darnos otra fórmula bien formada en notación polaca. Baste esta descripción para mostrar el carácter formal del procedimiento.
En la práctica, resultaría tedioso aplicar este procedimiento paso por paso. Resulta más adecuado entender la mecánica por medio de ejemplos y aplicarla directamente; con cierto entrenamiento la traducción se hace de manera casi automática.

Como ejemplo de este procedimiento vamos a utilizar una instancia de la ley de De Morgan, que en notación estándar escribiríamos así: ¬(p ∧ q) → (¬p ∨ ¬q). En este ejemplo la conectiva principal es la implicación, una conectiva binaria, cuyos argumentos tienen como conectiva principal la negación y la disyunción respectivamente. Tendremos que traducir las sucesivas subfórmulas hasta las variables variables proposicionales.

2.2.2 Código P 

El código P comenzó como un código ensamblador objetivo estándar producido por varios compiladores Pascal en la década de 1970 y principios de la de 1980. Fue diseñado para código real para una máquina de pila hipotética la idea era hacer que los compiladores de Pascal se transportaran fácilmente requiriendo solo que se volviera a escribir el intérprete de la maquina P para una plataforma, el código P también a probado ser útil como código intermedio y sean utilizado varias extensiones y modificaciones del mismo en diverso compiladores de código nativo, la mayor parte para lenguaje tipo Pascal. Como el código P fue diseñado para ser directamente ejecutable, contiene una descripción implícita de un ambiente de ejecución particular que incluye tamaños de datos, además de mucha información específica para la maquina P, que debe conocer si se desea que un programa de código P se comprensible. La máquina P está compuesta por una memoria de código, una memoria de datos no específica para variables nombre das y una pila para datos temporales, junto como cualquiera registro que sea necesario para mantener la pila y apoyar la ejecución. 


2.2.3 Triplos


• El resultado se asocia al número de tripleta Ejemplo:
 W * X + (Y + Z) 1. *, W, X 2. +, Y, Z 3. +, (1), (2) Control de flujo: 

IF X>Y THEN Z=X ELSE Z=Y+1 1. >, X, Y 
2. Saltar si falso, (1), 5 3. =, Z, X 
4. Saltar,, 7 5. +, Y, 1 6. =, Z, (5) Problema La optimización supone mover tripletas y hay que recalcular las referencias. 2.2.4 Cuádruplos 
, , , Ejemplo: (A+B)*(C+D)-E +, A, B, T1 +, C, D, T2 *, T1, T2, T3 -, T3, E, T4 Las cuádruplas facilitan la aplicación de muchas optimizaciones, pero hay que tener un algoritmo para la reutilización de las variables temporales (reutilización de registros del procesador). 

2.3 Esquema de generación 

Los esquemas de generación son las estrategias o acciones que se deberán realizarse y tomarse en cuenta en el momento de generar código intermedio. Los esquemas de generación dependen de cada lenguaje. Tomaremos algunos esquemas de generación del lenguaje C. 

2.3.1 Variables y constantes

 Las variables y constantes deben separarse de tal manera que queden las expresiones una por una de manera simple. Por ejemplo int a,b,c; se descompone a int a; int b; intc; respectivamente. 

2.3.2 Expresiones 

En esta función recibe una cadena que representa una línea de código intermedio y toma las medidas oportunas para que ese código se utilice. Estas medidas pueden ser escribir la línea en un fichero adecuado, almacenar la instrucción en una lista que después se pasará a otros módulos, o cualquier otra que necesitemos en nuestro compilador. Expresiones aritméticas Son aquella donde los operadores que intervienen en ella son numéricos, el resultado es un número y los operadores son aritméticos. Los operadores aritméticos más comúnmente utilizados son: +, - , * , / y %. Comenzamos el estudio por las expresiones aritméticas. Lo que tendremos que hacer es crear por cada tipo de nodo un método que genere el código para calcular la expresión y lo emita. Ese código dejará el resultado en un registro, cuyo nombre devolverá el método como resultado. Para reservar estos registros temporales, utilizaremos una función, reserva. En principio bastar ‘a con que esta función devuelva un registro distinto cada vez que se la llame. Cada nodo generará el código de la siguiente manera:  Por cada uno de sus operandos, llamara al método correspondiente para que se evalúe la sub expresión. Si es necesario, reservara un registro para guardar su resultado.  Emitirá las instrucciones necesarias para realizar el cálculo a partir de los operandos. 

2.3.3 Instrucciones de asignación 

La sintaxis general de la instrucción de asignación es: nombre_de_la_variable = valor El valor a la derecha del signo igual puede ser una constante, otra variable o una expresión que combine constantes y variables, pero siempre la variable y su valor deben ser del mismo tipo de dato. Ejemplos: edad% = 5 area! = 12.3 nombre$ = “Pedro”  Instrucciones de asignación compuesta Las instrucciones de asignación compuesta realizan primero una operación en una expresión antes de asignarla a un elemento de programación. En el siguiente ejemplo se muestra uno de estos operadores, +=, que incrementa el valor de la variable del lado izquierdo del operador con el valor de la expresión de la derecha. Una instrucción de asignación asigna el valor de una expresión a una variable. En general, si la variable que se va a asignar es una propiedad, la propiedad debe ser de lectura y escritura o de sólo escritura; en caso contrario, se produce un error de compilación. Si la variable es una variable de sólo lectura, la asignación debe producirse en un constructor Shared o un constructor de instancia apropiado para el tipo de la variable; en caso contrario, se producirá un error de compilación.

2.3.4 Instrucciones de control 

Esta forma de programación sólo permite resolver problemas sencillos. Para resolver problemas más complejos, nos puede interesar que dependiendo de los valores de los datos, se ejecuten unas instrucciones u otras. Las instrucciones condicionales nos van a permitir representar éste tipo de comportamiento. Sentencias IF y SWITCH. En otros casos, nos encontraremos con la necesidad de repetir una instrucción o instrucciones un número determinado de veces. En éstos casos utilizaremos instrucciones de control iterativas o repetitivas (ciclos). Sentencias WHILE, DO-WHILE y FOR. 

2.3.5 Funciones

 Las funciones pueden reducir a en línea, lo que se hace que expandir el código original de la función. Las funciones se descomponen simplificando los parámetros de manera individual al igual que el valor de retorno.