id:eel3:20150622:1434981139 の続きというかやり直しというか。
使用したソースファイルの内容や、ビルド時のコマンドなどについては、前回のエントリを参照してほしい。
前回の落穂拾い (1):Go言語で動的リンク
Go言語は、本家プロジェクトのコンパイラで普通にビルドすると、静的リンクの実行ファイルが生成される。
# go version go1.4.2 linux/386 go build hello.go mv hello hello_bin/hello_go
$ ldd hello_go 動的実行ファイルではありません $ _
オプション -ldflags を使ってリンカにオプションを設定することで、動的リンクの実行ファイルを生成できるようだ。
# go version go1.4.2 linux/386 go build -ldflags '-linkmode external' hello.go mv hello hello_bin/hello_go
$ ldd hello_go linux-gate.so.1 => (0xb774b000) libpthread.so.0 => /lib/i386-linux-gnu/libpthread.so.0 (0xb7719000) libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7570000) /lib/ld-linux.so.2 (0xb774c000) $ _
実行ファイルの大きさを比較すると、こんな感じ。
link type | not stripped | stripped (strip -s) |
---|---|---|
静的リンク | 1564840 | 1089912 |
動的リンク | 1582347 | 1091428 |
……あれ? 動的リンクの方が微妙に大きいぞ?
前回の落穂拾い (2):Rustをより動的リンクな感じにビルドする
前回、Rustのコードを次のようなコマンドでビルドした。
# rustc 1.0.0 (a59de37e9 2015-05-13) (built 2015-05-14) rustc -o hello_bin/hello_rust hello.rs
このビルドでは、動的リンクする対象は、システム(Linux)に用意されているごく普通のライブラリのみだった。
$ ldd hello_rust linux-gate.so.1 => (0xb7752000) libdl.so.2 => /lib/i386-linux-gnu/libdl.so.2 (0xb76e7000) libpthread.so.0 => /lib/i386-linux-gnu/libpthread.so.0 (0xb76cc000) librt.so.1 => /lib/i386-linux-gnu/librt.so.1 (0xb76c2000) libgcc_s.so.1 => /lib/i386-linux-gnu/libgcc_s.so.1 (0xb76a4000) libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb74fb000) /lib/ld-linux.so.2 (0xb7753000) libm.so.6 => /lib/i386-linux-gnu/libm.so.6 (0xb74cf000)
後で気づいたのだが、このビルド方法では、Rust本体にかかわるライブラリは静的リンクされ、実行ファイルに含まれている。
オプションとして prefer-dynamic を追加すると、Rustの言語本体のライブラリも動的リンクの対象となる。
# rustc 1.0.0 (a59de37e9 2015-05-13) (built 2015-05-14) rustc -C prefer-dynamic -o hello_bin/hello_rust hello.rs
ldd(1)してみると、動的リンクの対象として libstd-4e7c5e5c.so が増えている。
$ ldd hello_rust linux-gate.so.1 => (0xb7721000) libstd-4e7c5e5c.so => /usr/local/rust/lib/libstd-4e7c5e5c.so (0xb735b000) libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb719e000) libdl.so.2 => /lib/i386-linux-gnu/libdl.so.2 (0xb7198000) libpthread.so.0 => /lib/i386-linux-gnu/libpthread.so.0 (0xb717d000) librt.so.1 => /lib/i386-linux-gnu/librt.so.1 (0xb7174000) libgcc_s.so.1 => /lib/i386-linux-gnu/libgcc_s.so.1 (0xb7156000) /lib/ld-linux.so.2 (0xb7722000) libm.so.6 => /lib/i386-linux-gnu/libm.so.6 (0xb712a000) $ _
prefer-dynamic の有無で実行ファイルの大きさを比較すると、こんな感じ。
prefer-dynamic 有無 | not stripped | stripped (strip -s) |
---|---|---|
prefer-dynamic なし | 553685 | 321648 |
prefer-dynamic あり | 7593 | 5576 |
prefer-dynamic ありでnot strippedな状態での7,593 Byteという大きさは、gccで「動的リンク・最適化なし」でビルドしたC言語やC++の実行ファイルと同じくらいだ。
前回の落穂拾い (3):MKCLの言語本体のライブラリを実行ファイルに含めてしまう
今度は、前項のRustとは正反対のケース。
前回、MKCLでは次のような感じでビルドしていた。
# MKCL 1.1.9 mkcl # (compile-file # #P"./hello.lisp" # :output-file #P"./hello.o" # :fasl-p nil) # (compiler::build-program # "hello_mkcl" # :lisp-object-files '(#P"./hello.o")) # :exit mv hello_mkcl hello_bin/
結果として、動的リンクするライブラリに mkcl_1.1.9.so が含まれていた。
$ ldd hello_mkcl linux-gate.so.1 => (0xb76e6000) mkcl_1.1.9.so => /usr/local/mkcl/lib/mkcl_1.1.9.so (0xb72da000) libpthread.so.0 => /lib/i386-linux-gnu/libpthread.so.0 (0xb72ab000) libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7101000) libgmp.so.10 => /usr/lib/i386-linux-gnu/libgmp.so.10 (0xb7082000) librt.so.1 => /lib/i386-linux-gnu/librt.so.1 (0xb7079000) libdl.so.2 => /lib/i386-linux-gnu/libdl.so.2 (0xb7074000) libm.so.6 => /lib/i386-linux-gnu/libm.so.6 (0xb7048000) /lib/ld-linux.so.2 (0xb76e7000) $ _
次のようにビルドすると、 mkcl_1.1.9 のみ静的リンクされ、実行ファイルに含まれた状態となる。
# MKCL 1.1.9 mkcl # (compile-file # #P"./hello.lisp" # :output-file #P"./hello.o" # :fasl-p nil) # (compiler::build-program # "hello_mkcl" # :lisp-object-files '(#P"./hello.o") # :use-mkcl-shared-libraries nil) # :exit mv hello_mkcl hello_bin/
ldd(1)してみると、動的リンクの対象から mkcl_1.1.9.so が消えている。
$ ldd hello_mkcl linux-gate.so.1 => (0xb770c000) libgmp.so.10 => /usr/lib/i386-linux-gnu/libgmp.so.10 (0xb7677000) librt.so.1 => /lib/i386-linux-gnu/librt.so.1 (0xb766e000) libdl.so.2 => /lib/i386-linux-gnu/libdl.so.2 (0xb7668000) libm.so.6 => /lib/i386-linux-gnu/libm.so.6 (0xb763c000) libpthread.so.0 => /lib/i386-linux-gnu/libpthread.so.0 (0xb7621000) libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7478000) /lib/ld-linux.so.2 (0xb770d000) $ _
実行ファイルの大きさを比較すると、こんな感じ。
link type | not stripped | stripped (strip -s) |
---|---|---|
mkcl_1.1.9 を動的リンク | 57330 | 9760 |
mkcl_1.1.9 を静的リンク | 11922144 | 3470684 |
mkcl_1.1.9 を静的リンクすると、一気にでかくなりますな。strip前が11.9 MByte、strip -sの後でも3.3 MByte。うーん……。
計測結果:動的リンクの場合
気を取り直して、ビルドした実行ファイルの大きさをまとめてみた。
まずは、最適化無しで動的リンクでビルドした場合の大きさ。
言語 | ファイル名 | 大きさ(byte) |
---|---|---|
C言語 | hello_c | 7159 |
C++ | hello_cpp | 7754 |
D言語 | hello_d | 562112 |
Go言語 | hello_go | 1582347 |
Rust (prefer-dynamic あり) | hello_rust | 7593 |
Rust (prefer-dynamic なし) | hello_rust | 553685 |
OCaml | hello_ocaml | 142862 |
Common Lisp (ECL) | hello_ecl | 36667 |
Common Lisp (MKCL:mkcl_1.1.9 を動的リンク) | hello_mkcl | 57330 |
Common Lisp (MKCL:mkcl_1.1.9 を静的リンク) | hello_mkcl | 11922144 |
MKCL(mkcl_1.1.9 静的リンク版)が最も大きいというか、このでかさはなんなんでしょう?
次に、strip(1)でシンボルを削った状態での大きさ。strip -sで全てのシンボルを削除している。
言語 | ファイル名 | 大きさ(byte) |
---|---|---|
C言語 | hello_c | 5516 |
C++ | hello_cpp | 5588 |
D言語 | hello_d | 360500 |
Go言語 | hello_go | 1091428 |
Rust (prefer-dynamic あり) | hello_rust | 5576 |
Rust (prefer-dynamic なし) | hello_rust | 321648 |
OCaml | hello_ocaml | 105080 |
Common Lisp (ECL) | hello_ecl | 5616 |
Common Lisp (MKCL:mkcl_1.1.9 を動的リンク) | hello_mkcl | 9760 |
Common Lisp (MKCL:mkcl_1.1.9 を静的リンク) | hello_mkcl | 3470684 |
どれも小さくなっている。
Go言語が1.5 MByteから1.0 MByteと0.5 Mbyteも削れたが、それでもやはり大きい。ECLはC言語やC++並みの大きさになった。MKCL(mkcl_1.1.9 静的リンク版)は元の3分の1弱になる脅威の削減率を示したが、それでも3.3 MByteである。
計測結果:静的リンクの場合
次に、静的リンクでビルドした実行ファイルの大きさをまとめてみた。
残念ながら肝心のD言語とRustで静的リンクの実行ファイルを作れなかったので除外している(情報求ム)。ECLは、静的リンクで実行ファイルを作るには処理系をビルドし直す必要があったので、面倒なので止めた。
まずは、ビルド時のコマンド。
# gcc (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3 gcc -Wall -ansi -pedantic -static -o hello_bin/hello_c hello.c
# g++ (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3 g++ -Wall -ansi -pedantic -static -o hello_bin/hello_cpp hello.cpp
# go version go1.4.2 linux/386 go build hello.go mv hello hello_bin/hello_go
# The Objective Caml native-code compiler, version 3.12.1 ocamlopt -ccopt -static hello.ml mv a.out hello_bin/hello_ocaml
# MKCL 1.1.9 mkcl # (compile-file # #P"./hello.lisp" # :output-file #P"./hello.o" # :fasl-p nil) # (compiler::build-program # "hello_mkcl" # :lisp-object-files '(#P"./hello.o") # :use-mkcl-shared-libraries nil # :extra-ld-flags "-static") # :exit mv hello_mkcl hello_bin/
最適化無しで静的リンクでビルドした場合の大きさ。
言語 | ファイル名 | 大きさ(byte) |
---|---|---|
C言語 | hello_c | 751138 |
C++ | hello_cpp | 1441695 |
Go言語 | hello_go | 1564840 |
OCaml | hello_ocaml | 912359 |
Common Lisp (MKCL) | hello_mkcl | 13119102 |
Go言語以外、軒並み動的リンクの時よりも大きくなっている。
最小はC言語。C++が1.4 MByte弱と、Go言語に肉薄する大きさとなった。OCamlはC++より小さく、C言語に次ぐ2番目の大きさ。最大はMKCLの12.5 MByte。
次に、strip(1)でシンボルを削った状態での大きさ。strip -sで全てのシンボルを削除している。
言語 | ファイル名 | 大きさ(byte) |
---|---|---|
C言語 | hello_c | 684780 |
C++ | hello_cpp | 1192864 |
Go言語 | hello_go | 1089912 |
OCaml | hello_ocaml | 808560 |
Common Lisp (MKCL) | hello_mkcl | 4341672 |
最小のC言語、2番手のOCamlと、順序は変わらず。C++がGo言語よりもわずかに大きくなった結果、3〜4番手の順序が逆転。最大がMKCLなのは変わらないが、12.5 MByteから4.2 MByte弱へと、大幅なダイエットに成功している。
まとめ(という名の感想再び)
Go言語で動的リンクでビルドしても実行ファイルが小さくならない(それどころか微増する)のは衝撃的。おそらく言語本体や標準ライブラリの機能が丸ごと静的リンク状態で、現状ではどう頑張ってもその辺のライブラリが動的リンクにならないからだろう。この辺は、やはりgoogoの今後に期待、かなあ。
C++で静的リンクしたら、Go言語といい勝負な感じにファイルサイズが膨れ上がった。D言語やRustも静的リンクでビルドして、C++やGo言語と有意な差があるか見たかったが、結局できなかったのが残念。
OCamlはトップでも最下位でもなく、常に「中の上」に位置する謎の安定感があった。意外だが、実行ファイルの大きさという点では、もしかしたらD言語・Go言語・Rustよりマシかもしれない。
そしてMKCLに発覚した隠れ肥満疑惑。でもCommon Lispの処理系の中では小さいほうか?
動的リンク・静的リンクともに、当該言語のランタイムの大きさがキモなようだ。Rustのように、言語のランタイムまで動的リンクにするとCやC++相当の大きさになるケースや、MKCLのように言語のランタイムをリンクしたら激増したケースなど。判断がなかなか難しいなあ。