動的リンクと静的リンクの実行時のコスト差について

1秒間に1万件ほど処理するかなりハードなシステムで、あるライブラリを導入するときに「静的リンクじゃないと遅いよ?」と指摘をうけました。私は「ロードコストは確かにかかるかもしれないけど、なんで動的な方が遅いの?ロード後はアドレスジャンプだから実行時コストは変わらないのでは?」という疑問を持ちました。この答えを探した結果、結構長い調べ物になりました。

そもそもリンクとはなにをしているの?

分割コンパイルしている場合、それぞれのソースにたいしてオブジェクトファイルが生成され、そのオブジェクトファイル同士をリンクし実行ファイルを生成します。

オブジェクトファイルって?

コンパイラが解析翻訳した機械語や様々な情報のつまった中間ファイルです。そのままでは実行できません。linuxではELFというフォーマットが一般的です。

実はあれもこれもELFフォーマット

  • 実行可能ファイル
  • 共有オブジェクトファイル
  • コンパイラが作成するオブジェクトファイル

以上はすべてELFフォーマットです。
共有オブジェクトファイルというのは3番目のオブジェクトファイルをarコマンド(tarに似ている)でまとめたものです。

実際のELFファイルの中身

ELFファイルにはコンパイラが解析翻訳した機械語のほかに下記の情報を持っています。

  1. リロケータブル情報
    実行時にしか「何処の実アドレスを使うか?」は決まりません。そこでオブジェクトファイルは「とりあえず先頭は0から始まる」として生成されます。そこをどのように調整するか?のオフセット情報を持っており、実行時に有効な実アドレスへと書き換え(リロケーション)されます

    また動的ライブラリの場合、見つかっていない(名前未解決)シンボルだけのシンボルテーブルをもち、ローダーがこの未解決シンボルテーブルを参考にリロケーションを行います。

  2. シンボル情報
    関数や変数の名前や位置、スコープ、タイプなどの情報です。コンパイルが終了すれば破棄されますが、残す事も出来ます。デバッグや統計情報などで利用されます。

    またDLLではこのシンボル情報をもとにリローケションを行います。

    面白いもので、シンボル情報さえ持っていれば実行可能ファイルもDLLとして利用可能です。

  3. デバッグ情報

(参考:ELF入門 http://www-06.ibm.com/jp/domino01/mkt/cnpages7.nsf/ec7481a5abd4ed3149256f9400478d7d/4925722f004efe9249257658002cb129/$FILE/ELF_v1_0.pdf)


そもそもリンクとはなにをしているの?2

僕の知っているリンクとはコンパイルの時にオブジェクトファイルをリンクすることだけでした。しかし、静的や動的などリンクの方法は色々あります。そこで共有オブジェクトのリンクのされ方をきちんと調べてみました。

  1. 静的リンク
    実行可能ファイルのなかに共有オブジェクトファイルがコピーされ取り込まれます。取り込まれた分だけファイルサイズは大きくなります。共有オブジェクトがバージョンアップした場合は再度リンクし実行ファイルを作り直す必要があります。

  2. 動的リンク
    実行可能ファイルのなかで「どの共有オブジェクトのどの部分をつかうか」だけを記録し、実行可能ファイルの起動時にローダー(リンクしてくれるOSの機能)がリンク作業を行います。

  3. もっと動的リンク
    pluginなど、「どの共有オブジェクトのどの部分をつかうか」も動的に決めたい場合があります。その場合は明示的にロード作業を行い(dlopen()とかdlsym())、さらに動的にリンクすることも可能です。

(参考 http://akito.wiki.fc2.com/wiki/%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%AB%E3%83%BB%E3%83%AA%E3%83%B3%E3%82%AF)
(参考 http://ja.wikipedia.org/wiki/%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA)

僕の疑問の核心をつく記述がwikiにありました。

一部分を引用します。

ローダの処理は、メモリ上の各ライブラリの位置が実際にロードされるまで確定しないため、ちょっとしたトリックを必要とする。ディスク上のファイル内に絶対アドレスを書きこんでおくことはDLL内であっても不可能である。理論的にはメモリにロードされたときにライブラリを参照している部分を全て書き換えて正しいメモリ上の位置を参照するようにすることはできるが、それによって消費される時間とメモリは無視できない。その代わりに多くの動的リンクシステムではアドレス欄が空欄となったシンボルテーブルをコンパイル時に用意する。ライブラリへの参照は全てこのシンボルテーブルを経由して行われる(コンパイラはシンボルテーブルからアドレスを取り出して使うコードを生成する)。メモリにロードされたとき、ローダがこのテーブルを書き換える。

http://ja.wikipedia.org/wiki/%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA より

そうです。動的ロードの場合、関数の呼び出しコストとしてシンボルテーブルの経由コストがかかるのです。静的ロードの場合は全て実行可能ファイルに取り込まれているのでメモリ上のアドレス解決は問題ありません。というか静的ロードも、コンパイル時のオブジェクトファイルのリンクもやってる事は変わりなかったのです。

そしてようやく、「動的ロードの場合は静的ロードの場合よりもコストがかかる」という答えを得られました。

(参考 http://wiki.osdev.info/?ELF%2F%BC%C2%B9%D4%BB%FE%A4%CE%CF%C3)