Introducción a la optimización de software, consejos, metodología y tools.
Ejemplo y documentación en:
https://drive.google.com/open?id=13egVpX2uOSX7-BWyCvFrpZul6EOEM6_U
2. Índice
• Introducción
• Un poco de teoría
• Guía básica de viaje. Cómo afrontar una
optimización
• Estudio de un caso real
• Ejemplo práctico
• Enlaces interesantes
4. ¿Qué es la optimización de software?
• Proceso para mejorar la velocidad de ejecución, su tamaño
en disco ó las necesidades de memoria de un software.
• Puede realizarse en la creación del código, en un paso
posterior ó después de haberse finalizado la tarea.
• Puede ser un paso peligroso y nunca se puede asegurar la
optimización perfecta, aunque sí conlleva mejoras.
• Distinguiremos dos tipos: SOFT (optimizaciones sencillas,
que se realizan normalmente en el proceso de creación del
software) y HARD (el resto).
5. ¿Cuándo realizar una optimización?
Optimizaciones SOFT
• Cuando estamos escribiendo el código.
• Al continuar con una tarea (refactorización,
eliminación de código duplicado, etc…)
• Al solucionar bugs o problemas.
Optimizaciones HARD
• Nunca
• Sólo cuando sea estrictamente necesario y
analizando previamente si el cambio compensa
el riesgo:
• Si no se cumplen los requisitos de la
aplicación o del sistema para el que se
desarrolla.
• El rendimiento es crítico.
• Colapsa o deja inutilizable el sistema.
• Etc…
En general seguiremos un planteamiento conservador: Sólo se aplicarán las optimizaciones
“seguras”, en ningún caso aquellas que puedan ser peligrosas
6. Consejos generales
• La primera regla de la optimización es que no se habla de la
optimización (hasta que sea necesario).
• Diseña correctamente las aplicaciones, un buen diseño y estratificación
es indispensable.
• Analiza tus problemas antes de resolverlos, elige los mejores algoritmos
antes de implementar.
• Codifica correctamente con claridad y estilo, no es una pérdida de
tiempo.
• Prima siempre la calidad del código sobre la optimización (no ofusques).
Si no está roto, no lo arregles
Asegúrate de que el cambio que vas a realizar tendrá una
mejora sustancial respecto al software actual. Utiliza
herramientas externas para asegurarte de que es así
9. Ámbitos de optimización
Según arquitecturaSegún aplicación
Dependientes de la máquina
Independientes de la máquina
• Aprovechan las características
específicas de cada máquina.
• Uso de instrucciones especificas de
procesador.
• Predicción de saltos, optimización de
memoria, caché, etc…
• Se aplican a cualquier tipo de
máquina.
• Eliminación de redundancias, cambio
de orden de ejecuciones, etc...
Optimizaciones locales
• Se aplican dentro de un mismo
bloque de código.
• No tienen dependencias ni
repercusiones fuera del bloque de
aplicación.
Optimizaciones globales
• Aplican a más de un bloque.
• Tiene en cuenta el contenido y los
flujos de datos entre todos o parte
de los bloques de un programa..
• Necesita conocer las interrelaciones
entre los distintos bloques .
10. Buenas praxis al programar
• Elimina código innecesario.
• Saca código de los bucles.
• Inicializa las variables al crearlas.
• Divide y vencerás: Independencia de bloques,
zapatero a tus zapatos.
• Pasa los parámetros grandes por referencia.
• Reduce las operaciones de IO, trabaja en memoria.
• Usa los tipos de variables adecuados.
• Identifica constantes y variables estáticas.
• Utiliza las variables globales sólo cuando sean
necesarias.
• ¡Conoce tu entorno y herramientas!
• Un buen diseño y arquitectura inicial te ahorraran
muchos problemas.
• Codifica pensando en la claridad y no ofusques
intentando optimizar, el compilador también
trabaja.
11. typedef struct _my_struct
{
char name[50]; char comments[1024];
}my_struct;
void main()
{
int index;
float total;
bool found;
my_struct data[100];
…
index = 0;
total = 100;
InitializeData(&data);
…
for (index = 0; index < total; index++)
{
char name [] = “Laetitia”;
found = false;
if (IsSameName(name, data[index]))
{
if (found == false)
{
found = true;
break;
}
}
}
…
}
bool IsSameName(char* name, my_struct data)
Buenas praxis al programar (ejemplo)
typedef struct _my_struct
{
char name[50]; char comments[1024];
}my_struct;
void main()
{
int index;
float total;
bool found;
my_struct data[100];
…
index = 0;
total = 100;
InitializeData(&data);
…
for (index = 0; index < total; index++)
{
char name [] = “Laetitia”;
found = false;
if (IsSameName(name, data[index]))
{
if (found == false)
{
found = true;
break;
}
}
}
…
}
bool IsSameName(char* name, my_struct data)
typedef struct _my_struct
{
char name[50]; char comments[1024];
}my_struct;
void main()
{
int total = 100;
bool found = false;
my_struct data[100];
const char name [] = “Laetitia”;
…
InitializeData(&data);
…
for (int index = 0; index < total && !found; index++)
{
found =(IsSameName(name, data[index]));
}
…
}
bool IsSameName(const char* name, const my_struct& data)
12. Optimizaciones HARD
Optimizaciones dependientes del hardware o de muy alta dificultad.
• Se hacen siempre al finalizar la implementación.
• Abordar sólo las que sean completamente seguras, en ningún caso las inseguras.
• Ejemplos:
– Cambios en el orden de las operaciones.
– Optimizaciones para plataformas específicas (MMX, SSE, etc…)
– Cambios en algoritmos.
– Uso de modos de direccionamiento, asignación de registros, etc…
13. Herramientas y utilidades
Software libre
Software comercial
Compuware DevPartner Studio
IBM Rational
Intel V-Tune
Microsoft Visual Studio Ultimate
AMD Code Analyst
MemProf CCMGNU Prof
16. Determinar si existe un problema
Si no hay un problema, no toques nada.
Tenemos un problema si:
• La aplicación no cumple los requisitos iniciales.
• Algún proceso es tan lento que hace la aplicación inmanejable.
• Pierde memoria.
• Los accesos a medios externos provocan costes excesivos.
• El rendimiento se va degenerando cuanto más tiempo lleve ejecutándose el
programa.
• Es necesario aprovechar todos los recursos de la arquitectura sobre la que
ejecutamos la aplicación.
17. Encontrando el problema
• Intentar determinar el origen del problema:
• Buscar en la parte del código con la
funcionalidad a mejorar.
• Ejecutar la aplicación obviando los
bloques sospechosos.
• Verificar que se solucionan los problemas
de rendimiento.
• Si no es trivial encontrar el problema:
• Preparar un entorno de ejecución aislado.
• Si es posible implementar un tester que
ejecute únicamente las partes conflictivas
del programa.
• Utilizar una herramienta externa cómo un
profiler para obtener las estadísticas de
uso de bloques y tiempo que consumen.
18. Estudio y resolución del problema
Una vez encontrado el problema:
• Comprobar que es el problema real.
• Estudiar las posibles soluciones.
• Comprobar la viabilidad de las soluciones.
• De las posibles soluciones viables preparar una prueba de concepto para cada
una de ellas.
• Obtener estadísticas de rendimiento para cada prueba de concepto (hacer
varias ejecuciones en las mismas condiciones y comparar las medias).
• Escoger la mejor solución en cuanto rendimiento/facilidad de implementación.
• Implementar la solución en la aplicación final.
• Comparar las métricas de la aplicación original con la aplicación optimizada
para verificar que el problema se ha solucionado.
19. Comparando resultados
Es indispensable verificar la optimización:
• Prepara un entorno de pruebas limpio, una
máquina virtual con un punto de
restauración es ideal.
• Diseña un proceso de pruebas que verifique
el área concreta a optimizar, por ejemplo
con un tester de línea de comandos.
• Ejecuta un número de veces relevante las
pruebas con el sistema sin optimizar,
tomando las métricas adecuadas en cada
ejecución.
• Calcula la media de todas las ejecuciones
para cada métrica obtenida.
• Repite el proceso con el sistema optimizado.
• Compara los resultados entre ambos y
verifica que la optimización soluciona el
problema.
• Verifica que la solución es viable y es
posible incorporarla a la aplicación final.
21. Resultados de un estudio de
comparación de rendimiento
Escenario:
• Motor de detección viejo y difícil de mantener
• Motor no ampliable.
• Comienzo del desarrollo de un nuevo motor con arquitectura bien definida
• El nuevo motor debía de soportar plugins
• El nuevo motor debía añadir nuevas tecnologías de detección
• Requisito indispensable: Por lo menos igualar el rendimiento del motor actual
Motor actual 1.3 Motor 2.0 (B1)
Inicialización 527 ms. 1.200 ms.
Análisis x fichero +3 ms. 7 ms.
Análisis batería (100.000 ficheros) 6 min. 10 seg. 11 min. 45 seg.
Finalización 100 ms. 94 ms.
Tiempo total análisis 6 min. 20 seg. 12 min.
22. Análisis mediante y conclusiones
Consumo de tiempos (%)
Gestión TAD ficheros*
Análisis T1
Análisis T2
Análisis T3
Análisis T4
Análisis T5
Análisis T6
Operaciones IO
Función aux
Otros
*Gestión en memoria de ficheros a analizar
(búsquedas, accesos, obtener información…)
Conclusiones:
• Cuello de botella en la gestión de ficheros (el
tiempo debería de ser ínfimo en comparación
con las otras operaciones)
• Los análisis pueden verse lastrados por el
problema con la gestión de ficheros
• La función aux es trivial, algo raro está
pasando.
Medidas a tomar:
• Sustituir el TAD de la gestión de ficheros de
lista enlazada a tabla HASH.
• Revisar función aux.
• Eliminar redundancias encontradas.
• Eliminar múltiples lecturas de IO no necesarias.
• Eliminar múltiples llamadas a mismas
funcionalidades.
23. Resultados de la optimización
Conclusiones:
• Las pruebas de concepto resultaron satisfactorias.
• Se realizaron los cambios ideados durante el proceso de análisis.
• La mejora de rendimiento fue sustancial dejando al producto dentro de los
requerimientos iniciales.
• El proyecto salió adelante,
Motor actual 1.3 Motor 2.0 (B1) Motor 2.0 (B2)
Inicialización 527 ms. 1.200 ms. 890 ms.
Análisis x fichero +3 ms. 7 ms. 4 ms.
Análisis batería (100.000 ficheros) 6 min. 10 seg. 11 min. 45 seg. 6 min. 40 seg.
Finalización 100 ms. 94 ms. 74 ms.
Tiempo total análisis 6 min. 20 seg. 12 min. 6 min. 50 seg.