web-dev-qa-db-ja.com

GHCでコンパイルされた小さなHaskellプログラムが巨大なバイナリに

些細なHaskellプログラムでさえ、巨大な実行可能ファイルに変わります。

私は(GHCで)コンパイルされたサイズが7 MBのバイナリーの小さなプログラムを作成しました!

小さいHaskellプログラムでさえ巨大なバイナリにコンパイルされる原因は何ですか?

これを減らすために何かできることはありますか?

120
user181351

何が起こっているのか見てみましょう、試してみてください

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

ldd出力から、GHCが動的にリンクされた実行可能ファイルを生成したことがわかりますが、Cライブラリのみが動的にリンクされます! Haskellライブラリはすべてそのままコピーされます。

さておき:これはグラフィックを多用するアプリなので、間違いなくghc -O2でコンパイルします

できることは2つあります。

シンボルの除去

簡単な解決策:バイナリを削除します。

$ strip A
$ du -hs A
5.8M    A

Stripは、オブジェクトファイルからシンボルを破棄します。通常、これらはデバッグにのみ必要です。

動的にリンクされたHaskellライブラリ

最近、GHCは CとHaskellの両方のライブラリの動的リンク のサポートを獲得しました。ほとんどのディストリビューションは、Haskellライブラリの動的リンクをサポートするためにビルドされたGHCのバージョンを配布するようになりました。共有Haskellライブラリは、毎回実行可能ファイルにコピーすることなく、多くのHaskellプログラム間で共有できます。

執筆時点では、LinuxとWindowsがサポートされています。

Haskellライブラリを動的にリンクできるようにするには、次のように-dynamicを使用してコンパイルする必要があります。

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

また、共有したいライブラリは--enabled-sharedでビルドする必要があります:

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

そして、Cと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...

そして、ほら!

$ du -hs A
124K    A

ストリップしてさらに小さくすることができます:

$ strip A
$ du -hs A
84K A

動的にリンクされた多くのCおよびHaskellの部分から構築された、面倒なweensy実行可能ファイル:

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

最後のポイント:静的リンクのみのシステムでも、 se -split-objs を使用して、トップレベルの関数ごとに1つの.oファイルを取得できます。これにより、静的にリンクされたライブラリのサイズをさらに縮小できます。 -split-objsをオンにしてGHCを構築する必要がありますが、一部のシステムはこれを忘れています。

208
Don Stewart

Haskellはデフォルトで静的リンクを使用します。つまり、OpenGLへのバインディング全体がプログラムにコピーされます。それらは非常に大きいため、プログラムは不必要に膨張します。動的リンクを使用してこれを回避できますが、デフォルトでは有効になっていません。

11
fuz