読者です 読者をやめる 読者になる 読者になる

拾い物のコンパス

まともに書いたメモ

GHCでコンパイルしたバイナリを小さくする方法

概要

HaskellGHCコンパイルしてバイナリを生成することができる。しかし、Cの時と比べて意味不明なレベルでバイナリが大きくなる。原因と対策を書いておく。

環境

  • OS
$ uname -a
Linux ubuntu 3.13.0-63-generic #103-Ubuntu i686
$ ghc --version
The Glorious Glasgow Haskell Compilation System, version 7.6.3
$ gcc --version
gcc (Ubuntu 4.8.4-2ubuntu1~14.04) 4.8.4
...

ソース

-- hello.hs

main = putStrLn "Hello, World!"

-- E.O.F.
/* hello.c */
#include <stdio.h>

int main(void)
{
   printf("Hello, World!\n");
   return 0;
}
/* E.O.F. */

どちらも "Hello, World!"と出力するだけの簡単なプログラム。

コンパイル

  • GHC
    $ ghc -o h_hello hello.hs -O2

  • GCC
    $ gcc -o c_hello hello.c -O2

サイズ

$ ll ?_hello
7332     c_hello
839047   h_hello

HaskellはCの100倍以上の大きさを持っている。
最初見たときは叫んだ。

原因

GHCはデフォルトでHaskell runtime(libHSrts.a)を静的リンクするようだ。 こいつが丸々バイナリに組み込まれてしまうのが問題みたいだった。
GCCは最初から動的リンクするイケメン。

対策

動的リンクに切り替えてやればよい。
UbuntuHaskell platformには動的リンクが含まれていない。
だから最初に ghc-dynamic をインストールする。
$ apt-get install ghc-dynamic
それから以下を実行。

$ ghc -dynamic -o h_hello hello.hs`  
$ ll ?_hello
7332     c_hello
16397    h_hello

2倍程度まで小さくなった。 デバッグ情報を消去するともう少し小さくなる。参考までにデバッグ情報をなくしたCバイナリ(sc_hello)も載せておく。

$ strip -p --strip-unneeded --remove-section=.comment -o sh_hello h_hello
$ ll *_hello
7332     c_hello
16397    h_hello
5448     sc_hello
9688     sh_hello

c_helloのほぼ同程度のサイズまで落とせた。

結論

Haskellのバイナリはリンクさえどうにかすればサイズは2倍程度にできることがわかった。

おまけ

このままCバイナリに負けたままでは悔しかったからもう少し頑張ってみた。
実行形式ファイルを実行可能な圧縮ファイルに変換するgzexeというコマンドがある。これは実行ファイルをgzipで圧縮し、圧縮したファイルの先頭に解凍するスクリプトを埋め込む。容量の小さいディスクにおいて有効なコマンドである。

$ gzexe h_hello
h_hello:   74.2%

$ ll *_hello
7332     c_hello
5061     h_hello         <- 圧縮後
16397    h_hello~        <- 圧縮前

・・・ついにHaskellバイナリはCバイナリを越えた!

$ gzexe c_hello
c_hello    68.7%

$ ll *_hello
3132    c_hello         <- 圧縮後
7332    c_hello~

5061    h_hello         <- 圧縮後
16397   h_hello~

という夢を見た。おしまい。

メモ

最初にPreludeの関数をputStrLn以外をhidingしてみたが、サイズ面では全く変化がなかった。
関数のロード関連はサイズに関係しないフラグなどがあるのだろうか。

参考リンク

haskell - Could not find module Prelude... dyn libraries for package base? - Stack Overflow

linker - Making small haskell executables? - Stack Overflow