O pequeno programa Haskell compilado com o GHC em enormes arquivos binários

127

Mesmo programas trivialmente pequenos de Haskell se transformam em executáveis ​​gigantescos.

Eu escrevi um pequeno programa, que foi compilado (com GHC) no binário com o tamanho de 7 MB!

O que pode fazer com que até mesmo um pequeno programa Haskell seja compilado no enorme binário?

O que posso fazer para reduzir isso?

Marinheiro Danubiano
fonte
2
Você já tentou tirá-lo?
Fred Foo
21
Execute o programa stripno binário para remover a tabela de símbolos.
Fred Foo
1
@ tm1rbt: Corra strip test. Este comando remove algumas informações de depuração do programa e as reduz.
fuz 24/05
8
Além disso, seus tipos de dados na biblioteca de matemática 3D devem ser mais rígidos por razões de desempenho: data M3 = M3 !V3 !V3 !V3e data V3 = V3 !Float !Float !Float. Compile com ghc -O2 -funbox-strict-fields.
Don Stewart
8
Este post é discutido na meta .
Patrick Hofman

Respostas:

215

Vamos ver o que está acontecendo, tente

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

Você vê pela lddsaída que o GHC produziu um executável vinculado dinamicamente, mas apenas as bibliotecas C estão dinamicamente vinculadas ! Todas as bibliotecas Haskell são copiadas literalmente.

Além disso: como esse é um aplicativo com muitos gráficos, eu definitivamente compilaria com ghc -O2

Há duas coisas que você pode fazer.

Símbolos de remoção

Uma solução fácil: retire o binário:

$ strip A
$ du -hs A
5.8M    A

A faixa descarta símbolos do arquivo do objeto. Eles geralmente são necessários apenas para depuração.

Bibliotecas Haskell vinculadas dinamicamente

Mais recentemente, o GHC ganhou suporte para vinculação dinâmica das bibliotecas C e Haskell . A maioria das distros agora distribui uma versão do GHC criada para oferecer suporte à vinculação dinâmica de bibliotecas Haskell. As bibliotecas Haskell compartilhadas podem ser compartilhadas entre muitos programas Haskell, sem copiá-las no executável a cada vez.

No momento da escrita, Linux e Windows são suportados.

Para permitir que as bibliotecas Haskell sejam vinculadas dinamicamente, você precisa compilá-las da seguinte -dynamicmaneira:

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

Além disso, todas as bibliotecas que você deseja compartilhar devem ser construídas com --enabled-shared:

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

E você terminará com um executável muito menor, com as dependências C e Haskell resolvidas dinamicamente.

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

E, voilà!

$ du -hs A
124K    A

que você pode tirar para torná-lo ainda menor:

$ strip A
$ du -hs A
84K A

Um executável eensy weensy, construído a partir de várias partes C e Haskell vinculadas dinamicamente:

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

Um ponto final: mesmo em sistemas com vinculação estática, você pode usar -split-objs para obter um arquivo .o por função de nível superior, o que pode reduzir ainda mais o tamanho das bibliotecas vinculadas estaticamente. Ele precisa que o GHC seja construído com -split-objs, o que alguns sistemas esquecem de fazer.

Don Stewart
fonte
7
quando é que a ligação dinâmica deve chegar para o ghc no mac?
Carter Tazio Schonwald
1
... não cabal installtira o binário instalado por padrão?
hvr 25/05
1
fazê-lo no Windows parece fazer a un-executável arquivo resultante, ele reclama sobre a falta de libHSrts-ghc7.0.3.dll
is7s
3
este binário funcionará em outras máquinas Linux após estes procedimentos?
アレックス
1
Oi OP a partir de 2011! Sou do futuro e posso dizer que o executável pandoc no Ubuntu 16.04 tem 50 MB de gordura e não será alterado com base em packages.ubuntu.com/zesty/pandoc . Mensagem para um futuro próximo e para outros: entre em contato com o mantenedor do pacote e pergunte se enable-sharedfoi considerado. launchpad.net/ubuntu/+source/pandoc/+bugs
Stéphane Gourichon
11

Haskell usa vinculação estática por padrão. Ou seja, todas as ligações ao OpenGL são copiadas para o seu programa. Como eles são muito grandes, seu programa é inflado desnecessariamente. Você pode contornar isso usando o vínculo dinâmico, embora não esteja ativado por padrão.

difuso
fonte
5
Você pode vincular bibliotecas dinamicamente para contornar isso. Não sei por que importa o que é padrão, o sinalizador é bastante simples.
Thomas M. DuBuisson
4
O problema é que "todas as bibliotecas com as quais você deseja compartilhar devem ser construídas --enabled-shared", portanto, se a sua plataforma Haskell vier com bibliotecas criadas sem que --enabled sharedvocê precise recompilar as bibliotecas base, o que pode ser bastante doloroso.
Nspeccop 04/10/12