Pequeño programa Haskell compilado con GHC en un gran binario

127

Incluso los programas trivialmente pequeños de Haskell se convierten en ejecutables gigantes.

¡He escrito un pequeño programa, que fue compilado (con GHC) en el binario con un tamaño de 7 MB!

¿Qué puede causar que incluso un pequeño programa de Haskell se compile en el gran binario?

¿Qué puedo hacer para reducir esto?

Marinero danubiano
fuente
2
¿Has intentado simplemente desnudarlo?
Fred Foo
21
Ejecute el programa stripen el binario para eliminar la tabla de símbolos.
Fred Foo
1
@ tm1rbt: ejecutar strip test. Este comando elimina parte de la información de depuración del programa y la hace más pequeña.
fuz
8
Además, sus tipos de datos en la biblioteca matemática 3D deberían ser más estrictos por razones de rendimiento: data M3 = M3 !V3 !V3 !V3y data V3 = V3 !Float !Float !Float. Compilar con ghc -O2 -funbox-strict-fields.
Don Stewart
8
Esta publicación se discute en meta .
Patrick Hofman

Respuestas:

215

Veamos qué está pasando, intenta

  $ du -hs A
  13M   A

  $ file A
  A: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), 
     dynamically linked (uses shared libs), for GNU/Linux 2.6.27, not stripped

  $ ldd A
    linux-vdso.so.1 =>  (0x00007fff1b9ff000)
    libXrandr.so.2 => /usr/lib/libXrandr.so.2 (0x00007fb21f418000)
    libX11.so.6 => /usr/lib/libX11.so.6 (0x00007fb21f0d9000)
    libGLU.so.1 => /usr/lib/libGLU.so.1 (0x00007fb21ee6d000)
    libGL.so.1 => /usr/lib/libGL.so.1 (0x00007fb21ebf4000)
    libgmp.so.10 => /usr/lib/libgmp.so.10 (0x00007fb21e988000)
    libm.so.6 => /lib/libm.so.6 (0x00007fb21e706000)
    ...      

Como puede ver en el lddresultado, GHC ha producido un ejecutable vinculado dinámicamente, ¡pero solo las bibliotecas C están vinculadas dinámicamente ! Todas las bibliotecas de Haskell se copian al pie de la letra.

Aparte: dado que esta es una aplicación intensiva en gráficos, definitivamente compilaría con ghc -O2

Hay dos cosas que puedes hacer.

Símbolos de pelado

Una solución fácil: tira el binario:

$ strip A
$ du -hs A
5.8M    A

Strip descarta los símbolos del archivo objeto. Por lo general, solo son necesarios para la depuración.

Bibliotecas Haskell vinculadas dinámicamente

Más recientemente, GHC ha obtenido soporte para la vinculación dinámica de las bibliotecas C y Haskell . La mayoría de las distribuciones ahora distribuyen una versión de GHC creada para admitir el enlace dinámico de las bibliotecas Haskell. Las bibliotecas Haskell compartidas se pueden compartir entre muchos programas Haskell, sin copiarlas en el ejecutable cada vez.

Al momento de escribir, Linux y Windows son compatibles.

Para permitir que las bibliotecas de Haskell se vinculen dinámicamente, debe compilarlas de esta -dynamicmanera:

 $ ghc -O2 --make -dynamic A.hs

Además, todas las bibliotecas que desee compartir deben compilarse con --enabled-shared:

 $ cabal install opengl --enable-shared --reinstall     
 $ cabal install glfw   --enable-shared --reinstall

Y terminará con un ejecutable mucho más pequeño, que tiene las dependencias C y Haskell resueltas dinámicamente.

$ ghc -O2 -dynamic A.hs                         
[1 of 4] Compiling S3DM.V3          ( S3DM/V3.hs, S3DM/V3.o )
[2 of 4] Compiling S3DM.M3          ( S3DM/M3.hs, S3DM/M3.o )
[3 of 4] Compiling S3DM.X4          ( S3DM/X4.hs, S3DM/X4.o )
[4 of 4] Compiling Main             ( A.hs, A.o )
Linking A...

¡Y voilá!

$ du -hs A
124K    A

que puedes quitar para hacer aún más pequeño:

$ strip A
$ du -hs A
84K A

Un ejecutable eensy weensy, creado a partir de muchas piezas de C y Haskell vinculadas dinámicamente:

$ ldd A
    libHSOpenGL-2.4.0.1-ghc7.0.3.so => ...
    libHSTensor-1.0.0.1-ghc7.0.3.so => ...
    libHSStateVar-1.0.0.0-ghc7.0.3.so =>...
    libHSObjectName-1.0.0.0-ghc7.0.3.so => ...
    libHSGLURaw-1.1.0.0-ghc7.0.3.so => ...
    libHSOpenGLRaw-1.1.0.1-ghc7.0.3.so => ...
    libHSbase-4.3.1.0-ghc7.0.3.so => ...
    libHSinteger-gmp-0.2.0.3-ghc7.0.3.so => ...
    libHSghc-prim-0.2.0.0-ghc7.0.3.so => ...
    libHSrts-ghc7.0.3.so => ...
    libm.so.6 => /lib/libm.so.6 (0x00007ffa4ffd6000)
    librt.so.1 => /lib/librt.so.1 (0x00007ffa4fdce000)
    libdl.so.2 => /lib/libdl.so.2 (0x00007ffa4fbca000)
    libHSffi-ghc7.0.3.so => ...

Un último punto: incluso en sistemas con enlace estático solamente, puede usar -split-objs , para obtener un archivo .o por función de nivel superior, lo que puede reducir aún más el tamaño de las bibliotecas enlazadas estáticamente. Necesita que GHC se construya con -split-objs, algo que algunos sistemas olvidan hacer.

Don Stewart
fuente
77
¿Cuándo debe llegar el enlace dinámico para ghc en la mac?
Carter Tazio Schonwald
1
... no elimina cabal installel binario instalado por defecto?
hvr
1
hacerlo en Windows parece hacer que el archivo resultante no sea ejecutable, se queja de la falta de libHSrts-ghc7.0.3.dll
is7s
3
¿Este binario funcionará en otras máquinas Linux después de estos procedimientos?
ア レ ッ ク ス
1
Hola OP desde 2011! Soy del futuro y puedo decir que el ejecutable de Pandoc en Ubuntu 16.04 tiene 50 MB de grasa y no va a cambiar según los paquetes.ubuntu.com/zesty/pandoc . Mensaje para el futuro cercano y para los demás: comuníquese con el responsable del paquete y pregunte si enable-sharedfue considerado launchpad.net/ubuntu/+source/pandoc/+bugs
Stéphane Gourichon
11

Haskell usa enlaces estáticos por defecto. Es decir, todos los enlaces a OpenGL se copian en su programa. Como son bastante grandes, su programa se infla innecesariamente. Puede solucionar este problema mediante el uso de enlaces dinámicos, aunque no está habilitado de forma predeterminada.

fuz
fuente
55
Puede vincular dinámicamente bibliotecas para evitar esto. No estoy seguro de por qué importa lo que es predeterminado, la bandera es lo suficientemente simple.
Thomas M. DuBuisson
44
El problema es que "cualquier biblioteca con la que desee compartir debe construirse --enabled-shared", por lo que si su plataforma Haskell viene con bibliotecas creadas sin --enabled sharedque tenga que volver a compilar las bibliotecas base, lo que puede ser bastante doloroso.
nponeccop