Академический Документы
Профессиональный Документы
Культура Документы
LABORATORIO
Optimización de Algoritmos para
Aumentar el Rendimiento
Funciona revisando cada elemento de la lista que va a ser ordenada con el siguiente, intercambiándolos
de posición si están en el orden equivocado. Es necesario revisar varias veces toda la lista hasta que no
se necesiten más intercambios, lo cual significa que la lista está ordenada.
Este algoritmo obtiene su nombre de la forma con la que suben por la lista los elementos durante los
intercambios, como si fueran pequeñas "burbujas". También es conocido como el método del
intercambio directo.
Dado que solo usa comparaciones para operar elementos, se lo considera un algoritmo de comparación,
siendo el más sencillo de implementar.
Este algoritmo pasa por todos los elementos de la lista aún estando ordenada, esto supone que sea muy
ineficiente. La complejidad en el mejor de los casos es de Ω(n²).
2) Identificar el núcleo básico del algoritmo, aquél que consume la mayor parte del tiempo de
ejecución. Debe tratarse de una pequeña porción de código, entre 10 y 100 líneas de código:
3) Contar de forma teórica el número de operaciones básicas de cada tipo (lecturas en memoria,
escrituras en memoria, operaciones de cada tipo con valores enteros, operaciones de cada tipo en
punto flotante, etc.) que hay que realizar dentro de este núcleo básico del algoritmo:
El programa esta compuesto de dos bucles, recorremos el vector tantas veces como posiciones
contenga. Por tanto si tenemos un vector de n posiciones:
4) Identificar de forma teórica (analizando el código) el patrón de accesos a memoria (el orden de
las direcciones de memoria que el programa va accediendo):
Este programa esta compuesto por dos bucles, estos recorren el vector comparando cada posición con
la siguiente, intercambiándolas en caso de cumplirse la comparación.
Tras comprobar el algoritmo con el vector propuesto se realizarán 63 intercambios lo que conlleva
63*3 accesos a memoria adicionales.
La operación INT ( i++) del bucle externo se realizara n veces.
La operación INT ( j++) del bucle interno se realizara n*n veces.
54 7 2 33 9 12 2 3 1 26 4 67 87 34 22 97 5 29 54 123
PARALELISMO a nivel de INSTRUCCIÓN
El método de la burbuja es muy ineficiente, he aplicado dos cambios que mejorarán notablemente el
rendimiento:
• Cada iteración hay comparaciones de elementos consigo mismos, esto nos supone tiempo ya
que si tenemos un vector con 10000 elementos el programa realizará 10000 comparaciones
extra que se pueden simplificar.
• En vez de recorrer por parejas el vector en cada iteración del bucle externo, se puede modificar
y que coja la primera posición del vector comparándola con los elementos que tiene a su
izquierda que son los que ya están ordenados.
En el esquema podemos apreciar las dependencias de datos en una iteración, como podemos ver las tres
operaciones de memoria al hacer el intercambio deben ir seguidas porque dependen de la anterior. En
el instante en el que comparamos j=TAM? , si no ha llegado al valor de TAM se vuelve a empezar la
misma iteración desde la comparación a[j] > a[j+1] hasta la comparación j=TAM?.
En la mejora que he realizado se reduce el número de iteraciones ya que nos ahorramos comparar los
elementos ya ordenados, y comparar un elemento consigo mismo.
Las dependencias de control aparecen cuando la ejecución de la instrucciones i++ y j++ dependen de la
instrucción de salto condicional del bucle interno:
Porque la ejecución o no de i++ j++ depende de si el bucle interno finalmente salta o no salta
La segunda dependencia la tenemos en la linea 6, si se cumple ejecutaremos otra vez las lineas 1-6 sino
iremos a la 7.
Por último la tercera estará en la 8, si se cumple volveremos a ejecutar las lineas 1-6 sino acabamos el
bucle.
En este caso no conseguiremos nada porque los 2 bucles tienen el mismo recorrido, cambiar bucle
externo por el interno supone cambiar también las variables mas internas del bucle, en ese caso no
ocurre nada por lo dicho anteriormente.
4) Compilar el programa con las opciones de optimización –O2, –O3 y –O4. Observar el código
generado y tomar ideas.
-02 => Loop unrolling mínimo y trace scheduling (opción por defecto)
Total de ciclos -> 99661
Operaciones ejecutadas -> 43545
Numero total de saltos incondicionales -> 2703
Numero total de saltos incondicionales que saltan -> 2074
Fallos de caché de instrucciones -> 410
Fallos de cache de datos -> 214
Instrucciones con 1 operación -> 44,79%
Instrucciones con 2 operación -> 24,91%
Instrucciones con 3 operación -> 7,882%
Instrucciones con 4 operación -> 1911%
De esta manera para cada iteración del bucle hacemos 2 pasadas de las anteriores, así conseguimos que
el bucle se haga la mitad de veces.
S/W pipelining solapa completamente la ejecución de todas las iteraciones, excepto las primeras y las
últimas.
En este algoritmo el bucle interno, que es el que queremos aplicar software pipelining, esta compuesto
por instrucciones muy dependientes las unas de las otras, tras comprobar con el JavaVex que no
contiene muchos NOP, aplicar esta optimización no mejoraría notablemente el rendimiento.
No he encontrado la manera de realizar este apartado, según lo visto en teoría no he podido aplicarlo a
este algoritmo, tal vez porque no es posible.