Petit programme Haskell compilé avec GHC en énorme binaire

127

Même les programmes Haskell de petite taille se transforment en exécutables gigantesques.

J'ai écrit un petit programme, qui a été compilé (avec GHC) en binaire avec une taille de 7 Mo!

Qu'est-ce qui peut provoquer la compilation d'un petit programme Haskell en un énorme binaire?

Que puis-je faire, le cas échéant, pour réduire cela?

Marin danubien
la source
2
Avez-vous essayé de le décaper?
Fred Foo
21
Exécutez le programme stripsur le binaire pour supprimer la table de symboles.
Fred Foo
1
@ tm1rbt: Exécutez strip test. Cette commande supprime certaines informations de débogage du programme et le réduit.
fuz
8
En passant, vos types de données dans la bibliothèque mathématique 3D devraient être plus stricts pour des raisons de performances: data M3 = M3 !V3 !V3 !V3et data V3 = V3 !Float !Float !Float. Compilez avec ghc -O2 -funbox-strict-fields.
Don Stewart
8
Ce post est discuté sur meta .
Patrick Hofman

Réponses:

215

Voyons ce qui se passe, essayez

  $ 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)
    ...      

Vous voyez dans la lddsortie que GHC a produit un exécutable lié dynamiquement, mais seules les bibliothèques C sont liées dynamiquement ! Toutes les bibliothèques Haskell sont copiées in extenso.

A part: puisqu'il s'agit d'une application gourmande en graphiques, je compilerais certainement avec ghc -O2

Vous pouvez faire deux choses.

Symboles de dépouillement

Une solution simple: dépouiller le binaire:

$ strip A
$ du -hs A
5.8M    A

Supprimer supprime les symboles du fichier objet. Ils ne sont généralement nécessaires que pour le débogage.

Bibliothèques Haskell liées dynamiquement

Plus récemment, GHC a pris en charge la liaison dynamique des bibliothèques C et Haskell . La plupart des distributions distribuent désormais une version de GHC conçue pour prendre en charge la liaison dynamique des bibliothèques Haskell. Les bibliothèques Haskell partagées peuvent être partagées entre de nombreux programmes Haskell, sans les copier dans l'exécutable à chaque fois.

Au moment de l'écriture, Linux et Windows sont pris en charge.

Pour permettre aux bibliothèques Haskell d'être liées dynamiquement, vous devez les compiler avec -dynamic, comme ceci:

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

De plus, toutes les bibliothèques que vous souhaitez partager doivent être créées avec --enabled-shared:

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

Et vous vous retrouverez avec un exécutable beaucoup plus petit, qui a à la fois les dépendances C et Haskell résolues dynamiquement.

$ 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...

Et voilà!

$ du -hs A
124K    A

que vous pouvez décaper pour rendre encore plus petit:

$ strip A
$ du -hs A
84K A

Un exécutable très simple, construit à partir de nombreux éléments C et Haskell liés dynamiquement:

$ 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 dernier point: même sur les systèmes avec des liaisons statiques uniquement, vous pouvez utiliser -split-objs , pour obtenir un fichier .o par fonction de niveau supérieur, ce qui peut encore réduire la taille des bibliothèques liées statiquement. Il faut que GHC soit construit avec -split-objs, ce que certains systèmes oublient de faire.

Don Stewart
la source
7
Quand la liaison dynamique doit-elle arriver pour ghc sur le mac?
Carter Tazio Schonwald
1
... ne cabal installsupprime pas le binaire installé par défaut?
hvr
1
le faire sur Windows semble rendre le fichier résultant impossible à exécuter, il se plaint de l'absence de libHSrts-ghc7.0.3.dll
is7s
3
ce binaire fonctionnera-t-il sur d'autres machines Linux après ces procédures?
ア レ ッ ク ス
1
Salut OP de 2011! Je viens du futur et je peux dire que l'exécutable pandoc sur Ubuntu 16.04 fait 50 Mo de graisse et qu'il ne changera pas en fonction de packages.ubuntu.com/zesty/pandoc . Message à soi-même et aux autres dans un futur proche: contactez le responsable du paquet et demandez si cela a enable-sharedété envisagé. launchpad.net/ubuntu/+source/pandoc/+bugs
Stéphane Gourichon
11

Haskell utilise la liaison statique par défaut. C'est-à-dire que toutes les liaisons à OpenGL sont copiées dans votre programme. Comme ils sont assez gros, votre programme est inutilement gonflé. Vous pouvez contourner ce problème en utilisant la liaison dynamique, bien qu'elle ne soit pas activée par défaut.

fuz
la source
5
Vous pouvez lier dynamiquement des bibliothèques pour contourner ce problème. Je ne sais pas pourquoi la valeur par défaut est importante, le drapeau est assez simple.
Thomas M. DuBuisson
4
Le problème est que "toutes les bibliothèques que vous souhaitez partager doivent être construites avec --enabled-shared" donc si votre plateforme Haskell est livrée avec des bibliothèques construites sans que --enabled sharedvous ayez à recompiler les bibliothèques de base, ce qui peut être assez pénible.
nponeccop