martes, 31 de agosto de 2010

Aligerar programas en C

Programando me encuentro, después de unas "Merecidas" vacaciones, un programa que, estará colgado en mi blog dentro de poco (O eso espero)

Y me ha surgido una duda, la cual es; ¿Cómo aligerar los programas para mejorar los tiempos de ejecución?

Tras hacer varias pruebas con un procedimiento "Hi World", he conseguido mejorar los tiempos notablemente.

El programa con el que vamos a empezar a probar, es bastante simple. Basta con compilar y analizar, aquí el código fuente:

#include <stdio.h>

int main(void)
{
printf("Hola Mundo\n");
return 0;
}

Podemos analizar con gdb el comportamiento de nuestro programa:

(gdb) disas main
Dump of assembler code for function main:
0x080483e4 <+0>: push %ebp
0x080483e5 <+1>: mov %esp,%ebp
0x080483e7 <+3>: and $0xfffffff0,%esp
0x080483ea <+6>: sub $0x10,%esp
0x080483ed <+9>: movl $0x80484c0,(%esp)
0x080483f4 <+16>: call 0x8048318
0x080483f9 <+21>: mov $0x0,%eax
0x080483fe <+26>: leave
0x080483ff <+27>: ret
End of assembler dump.
En negrita, la llamada a la función puts, la cual se encarga de imprimir el mensaje en pantalla.

Ahora analicemos el tiempo de ejecución, con el comando time:

serch@serch-server:~/Escritorio$ time hw
hw: orden no encontrada

real 0m0.289s
user 0m0.200s
sys 0m0.080s

Como vemos es bastante ligero. Pero ahora, en vez de usar stdio y printf, vamos a comprobar el comportamiento con fprintf. Sustituimos printf por la función ya comentada y...

serch@serch-server:~/Escritorio$ time hw
hw: orden no encontrada

real 0m0.286s
user 0m0.224s
sys 0m0.060s
Como vemos, hemos aligerado "real" y "sys" (Aunque no mucha cantidad).

Veamos el disas de la modificación de nuestro programa:

(gdb) disas main
Dump of assembler code for function main:
0x08048414 <+0>: push %ebp
0x08048415 <+1>: mov %esp,%ebp
0x08048417 <+3>: and $0xfffffff0,%esp
0x0804841a <+6>: sub $0x10,%esp
0x0804841d <+9>: mov 0x804a020,%eax
0x08048422 <+14>: mov %eax,%edx
0x08048424 <+16>: mov $0x8048510,%eax
0x08048429 <+21>: mov %edx,0xc(%esp)
0x0804842d <+25>: movl $0xb,0x8(%esp)
0x08048435 <+33>: movl $0x1,0x4(%esp)
0x0804843d <+41>: mov %eax,(%esp)
0x08048440 <+44>: call 0x8048344
0x08048445 <+49>: mov $0x0,%eax
0x0804844a <+54>: leave
0x0804844b <+55>: ret
End of assembler dump.
Como vemos, hacemos muchos mas ciclos que con printf, aunque consumimos menos recursos. Tal y como observamos en negrita, ahora llamamos a fwrite. Para aligerar mas el programa, podríamos llamar directamente a write (En sistemas GNU/Linux, con la cabecera unistd).

Tras pasarnos al write, el código de nuestro programa queda así:

#include <stdio.h>

int main(void)
{
write(1, "Hola Mundo\n", 50);
return 0;
}

Veamos el disas de este código:

(gdb) disas main
Dump of assembler code for function main:
0x080483e4 <+0>: push %ebp
0x080483e5 <+1>: mov %esp,%ebp
0x080483e7 <+3>: and $0xfffffff0,%esp
0x080483ea <+6>: sub $0x10,%esp
0x080483ed <+9>: movl $0x32,0x8(%esp)
0x080483f5 <+17>: movl $0x80484d0,0x4(%esp)
0x080483fd <+25>: movl $0x1,(%esp)
0x08048404 <+32>: call 0x804830c
0x08048409 <+37>: mov $0x0,%eax
0x0804840e <+42>: leave
0x0804840f <+43>: ret
End of assembler dump.

Como vemos, ahora hacemos menos ciclos que antes. Veamos de nuevo el tiempo de ejecución:

serch@serch-server:~/Escritorio$ time hw
hw: orden no encontrada

real 0m0.302s
user 0m0.224s
sys 0m0.060s
¡¡No ha mejorado gran cosa!! Pero esto tiene explicación: Los que tengan la vista atenta, se habrán dado cuenta: He puesto el buffer de write a 50. Probemos con el buffer ajustado a la cadena de texto, osea, exactamente a 10.

serch@serch-server:~/Escritorio$ time hw
hw: orden no encontrada

real 0m0.289s
user 0m0.212s
sys 0m0.080s
Y ya hemos optimizado algo el tiempo de ejecución real.

Esto se nota poco en programas pequeños como este, pero a la hora de usar estas funciones en programas mas "grandes y serios", la cosa cambia, y mucho. Sin ir mas lejos, llamando directamente a write en una función, me he ahorrado mas de 300s, usando fprintf.

3 comentarios:

  1. Esto es lo bonito de la programación: hagas como hagas un programa, siempre puedes revisarlo un poco por pequeño que sea ^^

    ResponderEliminar
  2. Bueno, y esto es un hi world, cuando haces programas mas grandes, detalles como estos, aparte de evitarte muchos problemas, te restan un tiempo de ejecución enorme xD

    ResponderEliminar
  3. write .. me recuerda a fortran por la forma de usarse....

    ResponderEliminar