linker lernen - Kleines Haskell-Programm, das mit GHC zu einer großen Binärdatei kompiliert wurde




hello world (3)

Haskell verwendet standardmäßig statische Verknüpfungen. Das heißt, die gesamten Bindungen zu OpenGL werden in Ihr Programm kopiert. Da sie ziemlich groß sind, wird Ihr Programm unnötig aufgebläht. Sie können dies umgehen, indem Sie dynamisches Verknüpfen verwenden, obwohl es standardmäßig nicht aktiviert ist.

Selbst trivial kleine Haskell-Programme werden zu riesigen ausführbaren Dateien.

Ich habe ein kleines Programm geschrieben, das (mit GHC) auf die Binärdatei mit der Größe 7 MB kompiliert wurde!

Was kann dazu führen, dass selbst ein kleines Haskell-Programm in die riesige Binärdatei übersetzt wird?

Was, wenn überhaupt, kann ich tun, um dies zu reduzieren?


Mal sehen, was vor sich geht, versuch es

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

Sie sehen von der Ausgabe von ldd dass GHC eine dynamisch verknüpfte ausführbare Datei erzeugt hat, aber nur die C-Bibliotheken sind dynamisch verknüpft ! Alle Haskell-Bibliotheken werden wortwörtlich kopiert.

Nebenbei: Da es sich um eine grafikintensive App handelt, würde ich definitiv mit ghc -O2 kompilieren

Es gibt zwei Dinge, die Sie tun können.

Symbole entfernen

Eine einfache Lösung: strip die binäre:

$ strip A
$ du -hs A
5.8M    A

Streifen verwirft Symbole aus der Objektdatei. Sie werden in der Regel nur zum Debuggen benötigt.

Dynamisch verknüpfte Haskell-Bibliotheken

In jüngerer Zeit hat GHC Unterstützung für die dynamische Verknüpfung von C- und Haskell-Bibliotheken erhalten . Die meisten Distributionen vertreiben nun eine Version von GHC, die die dynamische Verknüpfung von Haskell-Bibliotheken unterstützt. Geteilte Haskell-Bibliotheken können unter vielen Haskell-Programmen geteilt werden, ohne sie jedes Mal in die ausführbare Datei zu kopieren.

Zum Zeitpunkt des Schreibens werden Linux und Windows unterstützt.

Damit die Haskell-Bibliotheken dynamisch verknüpft werden können, müssen Sie sie mit -dynamic kompilieren:

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

Außerdem sollten alle Bibliotheken, die Sie freigeben möchten, mit --enabled-shared :

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

Und Sie werden mit einer viel kleineren ausführbaren Datei enden, die C- und Haskell-Abhängigkeiten dynamisch aufgelöst hat.

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

Und, voilà!

$ du -hs A
124K    A

die Sie ausziehen können, um noch kleiner zu machen:

$ strip A
$ du -hs A
84K A

Ein eensy weensy ausführbares Programm, das aus vielen dynamisch verknüpften C- und Haskell-Stücken aufgebaut ist:

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

Ein letzter Punkt: Sogar auf Systemen mit nur statischer Verknüpfung können Sie -split-objs verwenden , um eine .o-Datei pro Top-Level-Funktion zu erhalten, was die Größe statisch verknüpfter Bibliotheken weiter reduzieren kann. Es muss GHC mit -split-objs aufgebaut werden, was einige Systeme vergessen.


Dadurch wird ghc statisch kompiliert (beachten Sie, dass der Pthread vor optl-static liegt): ghc --make -static -optl-pthread -optl-static test.hs

Edit: Aber die statische Kompilation scheint ein bisschen riskant zu sein. Meistens gibt es einige Fehler. Und auf meinem x64 Fedora funktioniert es überhaupt nicht. Die resultierende binäre ist auch ziemlich groß, 1,5 M für main = putStrLn "hello world"





haskell linker ghc static-linking glfw