1. ホーム
  2. haskell

[解決済み] GHCでコンパイルした小さなHaskellプログラムを巨大なバイナリにする

2022-06-23 17:27:10

質問

どんなに小さなHaskellプログラムでも、巨大な実行ファイルになってしまいます。

私は小さなプログラムを書きましたが、(GHCで)コンパイルされたバイナリは7MBにもなりました。

小さなHaskellのプログラムでも、巨大なバイナリにコンパイルされてしまう原因は何なのでしょうか?

これを減らすためにできることがあるとすれば、それは何でしょうか?

どのように解決するのですか?

何が起こっているのか、試してみましょう。

  $ 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

ストリップはオブジェクトファイルからシンボルを削除します。それらは一般にデバッグのためにのみ必要とされます。

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

より最近では、GHCは以下のものをサポートするようになりました。 のダイナミックリンクをサポートしました。 . ほとんどのディストロでは、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...

そして、voilà!

$ du -hs A
124K    A

で、さらに小さくするためにストリップすることができます。

$ strip A
$ du -hs A
84K A

多くの動的リンクされたCと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 => ...


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