lunes, 4 de agosto de 2014

Debugging - Optimizacion (I)

Empezamos una nueva serie de artículos  de la categoría de Debugging que va a tratar de la optimización de programas.

Todos tenemos claro que el objetivo de optimizar es realizar:  más cantidad / más rápido / mejor calidad con los  mismos o menores recursos.

En la caso de la programación la optimización pasa principalmente por hacer que los programas hagan sus tareas en el menor tiempo y consumiendo los menos recursos, aunque normalmente y a partir de determinado punto de finura ambos conceptos son antagónicos y hay que elegir.

Vamos a centrarnos es esta serie en la optimización como mejora de los tiempos de ejecución. Y vamos a ver como los compiladores nos ayudan en esta tarea.

Aunque siempre lo recuerdo, esta vez hago una vez más hincapié en ello: todos los resultados que se pueden obtener dependen en mayor medida del compilador que estemos usando y, en menor medida, de la versión del mismo.

La parte práctica de este articulo está hecha en el compilador GCC  Version: 4.8.1


En este enlace  tenemos todas las opciones de compilación de GCC, que no son pocas, y aunque se pueden tomar de forma independiente quienes han diseñado el compilador han estimado hacer tres grupos: 1, 2 y 3.
Además tenemos el 0 que quiere decir ninguna optimización y el s que quiere decir optimización de tamaño.

Cada grupo tiene sus propias opciones más las opciones del grupo anterior.

Vamos a trabajar con un sencillo programa que compilaremos, para no andarnos con sutilezas con las opciones más extremas: -O0 y -O3

El programa en cuestión es muy sencillo:

#include <stdio.h>
int sumaternaria( int a, int b, int c )
{
return a + b + c;
}
int main( void )
{
int a = 1;
int b = 2;
int c = 3;
int z = 0;
double  i,j;
for (i=0; i<10000000; i++) 
{   
for (j=0; j<100000; j++)
{
z = sumaternaria( 1, 2, 3);
}
}
printf( "Answer is: %d\n", z );
return 0;
}

Y parece díficil que se pueda optimizar. Este programa realiza una suma de tres número e imprime su resultado. Los dos bucles externos son para crear las suficientes iteraciones que puedan hacer el resultado perceptible.


Para poder trabajar he usado el compilador  y además de usar la opcion -O he utilizad la opción -S para que saque el listado en ensamblador que luego sirve de entrada al ensamblador propiamente dicho.

Además de la opción -O  para optimización, he usado la opción -S (mayúscula, no confundir con -s) para obtener el listado en ensamblado.

Para facilitar el trabajo he creado este script de compilación.

_programa=$1
_opcion=$2
echo "Compilando...."
gcc -O$_opcion -S -g -o $_programa$_opcion.s $_programa.c
echo "Ensamblando...."
as -alh -o $_programa$_opcion.obj $_programa$_opcion.s > $_programa$_opcion.asm
echo "Creando el ejecutable"
gcc $_params  -O$_opcion    -g -o $_programa$_opcion.exe  $_programa.c

Que es invocado de la siguiente forma:

./gcccomp.sh nombre_programa [0|3]

Obtengo los siguientes ficheros:

Programa compilado con la opción -O0
  • nombreprograma0.exe
  • nombreprograma0.asm
Programa compilado con la opción -O0
  • nombreprograma3.exe
  • nombreprograma3.asm

He realizado varias  compilaciones y ejecuciones modificando los valores de la variable j y dejando la variable i sin modificar. En mi equipo obtuve los siguientes resultados:

j = 1000

  • Compilación -O0: 37 segundos
  • Compilación -O3:  < 1 segundo


j: 10000

  • Compilación -O0: 375 segundos
  • Compilación -O3:  129 segundos


j: 100000 

  • Compilación -O0: 93 minutos
  • Compilación -O3:  42 minutos
Ya solo nos queda ver como se ha operado este "milagro" pero eso lo veremos en el siguiente artículo. Mientras tanto animo a los lectores que van siendo cada vez más a realizar las misma pruebas en sus equipos, (se puede realizar tanto en Windows como Linux), y a comparar los resultados obtenidos. 

No hay comentarios:

Publicar un comentario