Mały program Haskell skompilowany z GHC do ogromnego pliku binarnego

127

Nawet banalnie małe programy Haskell zamieniają się w gigantyczne pliki wykonywalne.

Napisałem mały program, który został skompilowany (z GHC) do pliku binarnego o rozmiarze do 7 MB!

Co może spowodować, że nawet mały program Haskell zostanie skompilowany do ogromnego pliku binarnego?

Co, jeśli cokolwiek, mogę zrobić, aby to zmniejszyć?

Żeglarz Naddunajski
źródło
2
Czy próbowałeś po prostu go rozebrać?
Fred Foo
21
Uruchom program stripna pliku binarnym, aby usunąć tablicę symboli.
Fred Foo
1
@ tm1rbt: Uruchom strip test. To polecenie usuwa niektóre informacje debugowania z programu i zmniejsza je.
fuz
8
Poza tym typy danych w bibliotece matematycznej 3D powinny być bardziej rygorystyczne ze względu na wydajność: data M3 = M3 !V3 !V3 !V3i data V3 = V3 !Float !Float !Float. Skompiluj z ghc -O2 -funbox-strict-fields.
Don Stewart
8
Ten post jest omawiany na meta .
Patrick Hofman,

Odpowiedzi:

215

Zobaczmy, co się dzieje, spróbuj

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

Na podstawie danych lddwyjściowych widać, że GHC utworzył dynamicznie połączony plik wykonywalny, ale tylko biblioteki C są połączone dynamicznie ! Wszystkie biblioteki Haskell są kopiowane dosłownie.

Poza tym: ponieważ jest to aplikacja intensywnie graficzna, zdecydowanie bym się z nią skompilował ghc -O2

Możesz zrobić dwie rzeczy.

Usuwanie symboli

Proste rozwiązanie: usuń plik binarny:

$ strip A
$ du -hs A
5.8M    A

Strip usuwa symbole z pliku obiektowego. Zwykle są potrzebne tylko do debugowania.

Dynamicznie połączone biblioteki Haskell

Niedawno GHC zyskało obsługę dynamicznego łączenia bibliotek C i Haskell . Większość dystrybucji dystrybuuje teraz wersję GHC zbudowaną do obsługi dynamicznego łączenia bibliotek Haskell. Współdzielone biblioteki Haskell mogą być współużytkowane przez wiele programów Haskell, bez konieczności kopiowania ich za każdym razem do pliku wykonywalnego.

W chwili pisania tego tekstu obsługiwane są systemy Linux i Windows.

Aby umożliwić dynamiczne łączenie bibliotek Haskell, musisz je skompilować za pomocą -dynamic, na przykład:

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

Ponadto wszystkie biblioteki, które chcesz udostępniać, powinny być zbudowane z --enabled-shared:

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

Otrzymasz znacznie mniejszy plik wykonywalny, który ma dynamicznie rozwiązane zależności zarówno C, jak i Haskell.

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

I voilà!

$ du -hs A
124K    A

które możesz rozebrać na jeszcze mniejsze:

$ strip A
$ du -hs A
84K A

Eensy weensy plik wykonywalny, zbudowany z wielu dynamicznie połączonych fragmentów C i Haskell:

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

I ostatnia uwaga: nawet w systemach z łączeniem statycznym można użyć opcji -split-objs , aby uzyskać jeden plik .o na funkcję najwyższego poziomu, co może dodatkowo zmniejszyć rozmiar bibliotek połączonych statycznie. Wymaga zbudowania GHC z -split-objs, o czym niektóre systemy zapominają.

Don Stewart
źródło
7
kiedy dynamiczne łączenie ma pojawić się w ghc na komputerze Mac?
Carter Tazio Schonwald
1
... nie usuwa cabal installdomyślnie zainstalowanego pliku binarnego?
hvr
1
robienie tego w systemie Windows wydaje się uniemożliwiać uruchomienie pliku wynikowego; narzeka na brak biblioteki libHSrts-ghc7.0.3.dll
is7s
3
czy ten plik binarny będzie działał na innych komputerach z systemem Linux po wykonaniu tych procedur?
ア レ ッ ク ス
1
Cześć OP od 2011! Jestem z przyszłości i mogę powiedzieć, że plik wykonywalny pandoc na Ubuntu 16.04 ma 50 MB tłuszczu i nie zostanie zmieniony w oparciu o packages.ubuntu.com/zesty/pandoc . Wiadomość do siebie i innych osób w najbliższej przyszłości: skontaktuj się z opiekunem pakietu i zapytaj, czy enable-sharedzostała uwzględniona. launchpad.net/ubuntu/+source/pandoc/+bugs
Stéphane Gourichon
11

Haskell domyślnie używa statycznego łączenia. Oznacza to, że całe powiązania z OpenGL są kopiowane do twojego programu. Ponieważ są one dość duże, program niepotrzebnie się zawyża. Możesz obejść ten problem, używając dynamicznego łączenia, chociaż nie jest ono domyślnie włączone.

fuz
źródło
5
Aby obejść ten problem, można dynamicznie łączyć biblioteki. Nie jestem pewien, dlaczego ma znaczenie, co jest domyślne, flaga jest dość prosta.
Thomas M. DuBuisson
4
Problem polega na tym, że „wszystkie biblioteki, z którymi chcesz się dzielić, powinny być zbudowane --enabled-shared”, więc jeśli Twoja platforma Haskell zawiera biblioteki zbudowane bez --enabled sharedkonieczności ponownej kompilacji bibliotek podstawowych, co może być dość bolesne.
nponeccop