Liposucciones de bytes

Hace unos meses, a raíz de un correo de Jesús Sanz comentando la mítica entrevista a BJarne Stroustrup , hicimos una pequeña prueba de cuánto podría engordar un “Hola, mundo!” en función del compilador y lenguaje de programación que utilices. De la exageración -casi irreal hoy en día- de 2.1 MB de ejecutable que prácticamente no hace nada (digo “casi irreal” porque he visto ejecutables que no hacían prácticamente nada más que lanzarse a ejecución con lorzas de bytes similares a esas), llegamos a los 177 bytes de “Hola, mundo!” (aunque estoy convencido de que se puede rebajar bastante más, y no me refiero a usar el formato COM, lenguajes interpretados o trucos similares :-D).

Veamos cómo fue la “operación bikini” con nuestros programas…

Todo comenzó con una frase de neo2001 que decía incrédulo: “De todas maneras… por morbo y porque me parecía una pasada, he compilado un helloWorld con el gcc 4.0 (creo) de Xcode y ocupa 38.796 bytes el binario… no 2.1 MB como dice este señor… vamos, no se a que se referirá…. pero creo que no se sostiene“.

Es una broma… que da qué pensar, ¿eh?

¿38.796 bytes un helloWorld y te parece poco? El mundo está loco.

Lo mismo en C ocupa…

$ echo 'main() { printf("Hello, world\n"); }' > helloC.c

$ make helloC
cc     helloC.c   -o helloC

$ ls -l helloC
-rwxr-xr-x 1 txipi txipi 6922 2006-05-28 00:19 helloC

6922 bytes, 6 veces menos!!

Pero es que si lo hacemos en ASM…

$ cat helloASM.asm
extern printf, exit

section .text

global _start
_start:

   push dword formatstring
   call printf
   push 0
   call exit

section .data

formatstring: db "Hello, world",10,0

Lo ensamblamos…

$ nasm -f elf helloASM.asm -o helloASM.o
$ ld -s --dynamic-linker /lib/ld-linux.so.2 -lc helloASM.o -o helloASM

Y lo ejecutamos…

$ ./helloASM
Hello, world

Miramos cuanto ocupa…

$ ls -l helloASM
-rwxr-xr-x 1 txipi txipi 1412 2006-05-28 00:25 helloASM

Ocupa 1412 bytes, unas 5 veces menos que en C y unas 30 veces menos que en C++.

Pero si es que en lugar de usar funciones de C como printf usamos las llamadas al sistema, nos ahorramos algo de espacio…

$ cat helloSyscalls.asm
section .text

global _start
_start:

   mov  eax, 04h
   mov  ebx, 01h
   mov  ecx, formatstring
   mov  edx, 13
   int  80h

   mov  eax, 01h
   mov  ebx, 00h
   int  80h

section .data

formatstring: db "Hello, world",10,0

Lo ensamblamos, enlazamos y ejecutamos…

$ nasm -f elf helloSyscalls.asm -o helloSyscalls.o
$ ld helloSyscalls.o -o helloSyscalls
$ ./helloSyscalls
Hello, world

Miramos cuanto ocupa…

$ ls -l helloSyscalls
-rwxr-xr-x 1 txipi txipi 811 2006-05-28 00:30 helloSyscalls

¡Aproximadamente la mitad que antes!

Pero aún hay más, si le pasamos el sstrip, para quitar secciones y strings que no sirven en tiempo de ejecución…

$ sstrip helloSyscalls
$ ls -l helloSyscalls
-rwxr-xr-x 1 txipi txipi 177 2006-05-28 00:35 helloSyscalls

177 bytes!!!

Frente a los 39 KB de C++, hay diferencia 😀

Posteriormente, entre los contertulios del hilo de correos, hicimos una recopilación de nuestras indagaciones, bastante reveladoras:

  • Windows Borland C++ Builder: 311.296 bytes
  • g++ 4.0 de Xcode (C++): 38.796 bytes
  • Borland 3.1 con cout (C++): 23.798 bytes
  • Borland 3.1 con printf (C): 8.992 bytes
  • gcc 4.0 (C): 6.922 bytes
  • nasm con printf: 1.412 bytes
  • nasm con syscalls: 811 bytes
  • nasm con syscalls, stripped: 177 bytes

311296 frente a 177 bytes, ¡¡¡un factor de “compresión” de 1758 a 1!!! (ya, ya, es un ejemplo farsa, pero me gusta poner voz de vendedor de teletienda 😉 ).

6 pensamientos en “Liposucciones de bytes

  1. Saladino

    Hombre, yo acabo de hacer pruebilla, y el de c tambien solo con strippearlo pierde mas de la mitad!

    saladino@Marley:~$ ls -al helloC
    -rwxr-xr-x 1 saladino saladino 6894 2006-09-02 00:25 helloC
    saladino@Marley:~$ strip helloC
    saladino@Marley:~$ ls -al helloC
    -rwxr-xr-x 1 saladino saladino 2960 2006-09-02 00:25 helloC

    Ñi

    Responder
  2. txipi

    Sí, está claro que un strip-tease del binario lo reduce bastante, pero yo creo que esa reducción no es proporcional, sino que siempre está en torno a un valor, que tendrá que ver con la cantidad de información supérflua del binario y que crece a mucho menor ritmo que el propio binario. Por eso al strippear el binario de 811 bytes se notó un cambio tan espectacular (a menos de la 4ª parte).

    Muchas gracias por compartir tus pruebas 😉

    Responder
  3. Blacky

    hey txipi 😀 jejejeje me mola me mola cada dia mas .. lo se … es un comentario demasiado estupido … he intentado leer algo … lo juro … pero es q me parece q leo chino macho … esta claro q no es lo mio … pero esta todo muy bonito 😀

    Responder
  4. Xavier

    Pués a mi con el GNU C++ 4.0.1 en Mandriva 2006 me da:

    $ g++ -o hola CPP-hola.cpp
    $ gcc -o holaC C-hola.c
    $ ls -all hola*
    -rwxrwxr-x 1 xavier xavier 6973 oct 8 16:22 hola*
    -rwxrwxr-x 1 xavier xavier 6572 oct 8 16:30 holaC*

    O sea no demasiado lejos de C pero un poco superior. Lo que si veo és que ‘strip’ les hace un lifting parecido

    $ ls -all hola*
    -rwxrwxr-x 1 xavier xavier 3452 oct 8 16:28 hola*
    -rwxrwxr-x 1 xavier xavier 3120 oct 8 16:28 holaC*

    I añado otra prueba, esta vez Microsoft only, con Visual Studio 2005 Express Edition en versión Release: 5.632 bytes

    Responder
  5. josejuan

    using System;
    namespace H {
    class Program {
    static void Main( string [] args ) {
    Console.WriteLine( “Hello World!” );
    }
    }
    }

    $csc /platform:x86 /debug- /checked- /nostdlib- Program.cs

    12/05/2010 12:03 3.584 Program.exe

    Responder

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *