2005年12月12日

prelink の効果を測定する

prelink は大量の共有ライブラリをリンクしたプログラムの起動時間を短縮するためのツールです。最近の Linux で利用できます。Gentoo Linux Prelink ガイドによると「典型的なKDEプログラムの起動時間は50%も短縮することができます」とのことです。このエントリでは簡単なプログラムを書いて prelink の効果を調べる実験を行ってみます。

 

通常、動的リンクされたプログラムはシンボルの参照時に再配置およびシンボルのルックアップなどを行う必要があります。prelink は実行ファイルと依存する共有ライブラリを書き換えて、これらの処理を行う必要性を減らします。また、prelink されたプログラムはリロケーションによって発生する共有不可能ページが減るため、プロセス間で共有できるページが増えるというという効果もあります。

prelink の効果を測定するために次のような実験を行いました。

  1. 1,000個の .c ファイルを生成する。個々のファイルには int func00XXX() { return 0; } という関数を含める。
  2. 上記の .c ファイルを gcc -shared でコンパイルして 1,000個の .so ファイルを作る
  3. 空っぽの main() しかないプログラムに上記の 1,000個の .so ファイルをリンクする
  4. 出来上がったバイナリの実行時間を計測する
  5. prelink 後のバイナリの実行時間を計測する
  6. 静的リンクで作ったバイナリの実行時間を計測する

結果は、次のようになりました。

prelink前 prelink後 静的リンク
約4.5秒 約0.5秒 約0.0秒

実験はコマンドラインからは次のように行いました。

# Debian に prelink をインストール
% sudo apt-get install prelink

# ファイルの生成とコンパイル
% time ruby prelink-test.rb

# prelink 前
% repeat 3 time ./test-dynamic
4.37s total : 4.22s user 0.15s system 100% cpu
4.62s total : 4.41s user 0.19s system 99% cpu
4.45s total : 4.28s user 0.17s system 99% cpu

# prelink を適用
% time prelink -N ./test-dynamic *.so
7.06s total : 5.52s user 1.85s system 104% cpu

# prelink 後
% repeat 3 time ./test-dynamic
0.51s total : 0.47s user 0.04s system 100% cpu
0.46s total : 0.43s user 0.03s system 99% cpu
0.49s total : 0.46s user 0.03s system 99% cpu

# 静的リンク
% time ./test-static
0.00s total : 0.00s user 0.00s system 0% cpu

prelink-test.rb のコードは以下の通りです。

system("rm -f *.c *.so")
File.open("test.c", "w") {|f|
  f.puts('int main() { return 0; }')
}

objs = []
dsos = []
1000.times {|i|
  c_file_name = sprintf("%05d.c", i)
  puts c_file_name
  File.open(c_file_name, "w") {|f|
    # f.printf('const char *s%05d = "%s";' + "\n", i, "o" * (1<<20));
    f.printf("int func%05d() { return 0; }\n", i);
  }
  obj_file_name = sprintf("%05d.o", i)
  dso_file_name = sprintf("%05d.so", i)
  system(sprintf("gcc -c %s", c_file_name))
  system(sprintf("gcc -fPIC -shared -o %s %s", dso_file_name, c_file_name))
  objs.push(obj_file_name)
  dsos.push("./" + dso_file_name)
}

system(sprintf("gcc -o test-static test.c %s", objs.join(" ")))
system(sprintf("gcc -o test-dynamic test.c %s", dsos.join(" ")))

# prelink -N test-dynamic *.so

まとめ

prelink を使うと大量の共有ライブラリをリンクしたプログラムの起動時間を短縮できることがわかりました。OpenOffice.org や Firefox などの大規模なアプリケーションでは prelink の効果は大きいと思います。とりわけ、C++ ではシンボル名が長くなり、共通する長い prefix を持つ傾向があるため、シンボルのルックアップが減る効果は大きそうです。

ところで、手元の環境 (i386 上の Debian GNU/Linux sarge) では prelink は 0x41000000 ~ 0x50000000 という 240 MB 分の仮想アドレス空間に共有ライブラリをレイアウトします。このため、240 MB に収まりきらない量の共有ライブラリを prelink しようとすると、「Could not find virtual address slot for ./foo.so」のようなエラーになります。

リンク