彷徨えるフジワラ

年がら年中さまよってます

OS毎のシステムコール実行性能 〜 その1

Mercurial のテストセット ("make tests") を複数の環境で実行したところ、ほぼ同一性能の CPU 上での実行にも関わらず、Mac OS X 上での実行では、仮想環境上の Linux での実行よりも、大幅に時間を要することが確認できました。

仮想環境上の実行ではクロック情報の精度がよろしくないので、計測主体が仮想環境上にあるベンチマーク結果はあまり信用できない、ということを考慮に入れても、明らかに体感できるレベルで遅い(分単位の所要時間差)ので、単純に「仮想環境上の計測誤差が、良い方向に出ている」と片付けるわけにも行きません。

そこで、幾つかのシステムコールに関して、OS 間での実行性能を計測してみることにしました。

元々の比較対象が「Mercurial のテストセット」なので、比較的影響が高そうな、以下のシステムコールを計測対象にします。

  • fork/exec 等によるプロセス生成
    テスト手順スクリプトを元に、コマンド起動〜結果比較を実施しているため
  • lstat/fstat 等によるファイルシステムアクセス
    「ファイルの履歴管理」という性格上、ファイルシステムアクセスの全体に占める比率は高い筈

各 OS の実行環境は以下の通りです。個人的な趣味の関係で、Solaris も計測対象に加えています(笑)。

---- MacOSX Linux Solaris
OS 詳細 Lion (10.7.5) Debian (2.6.32-5) OpenIndiana (151.a2)
CPU Core i5 2415M (2.3GHz) Core i5 2410M (2.3GHz)
利用可能コア/スレッド数 4 2
メモリ 16GB 2GB
インストール形態 ベアメタル 仮想環境
(VirtualBox on Windows7 64bit)

Core i5 2415M と 2410M は、ターボブースト時のクロックが前者の方が若干高い可能性がある以外は、整数演算に関しては同一性能と考えて良い筈です。

execve の性能比較

execve システムコールの性能比較として、以下の様なプログラムを実行してみます。

  1. 引数文字列を整数に変換
  2. 0 なら終了
  3. それ以外なら、1 を減じた数の文字列を引数に、自分自身を execve

繰り返し回数 0x10000 = 約6万4千の実行に要する時間 (単位:秒) は、以下の様な結果でした。

---- MacOSX Linux Solaris
execve 157.19 22.02 39.41

Mac OS X での execvc の実行は、Linux と比較して7倍程度の時間を要しています。

fork の性能比較

fork システムコールの性能比較として、以下の様なプログラムを実行してみます。

  1. 引数文字列を整数に変換
  2. 0 なら終了
  3. それ以外なら、fork を実施
  4. 子プロセスは、即時 _exit
  5. 親プロセスは、子プロセスを wait
  6. 指定整数から 1 を減じて、手順2から繰り返し

繰り返し回数 0x10000 = 約6万4千の実行に要する時間 (単位:秒) は、以下の様な結果でした。

---- MacOSX Linux Solaris
fork 27.36 9.35 24.32

Mac OS X での fork の実行は、Linux と比較して3倍弱の時間を要しています。

fork + execve の性能比較

ここまでの計測で、Macs OS X で fork および execve の実行が、Linux と比較して共に大幅に時間を要することが既にわかっていますが、fork + execve を対で実施した場合の所要時間も、念のため測っておくことにしましょう。

fork + execve システムコールの性能比較として、以下の様なプログラムを実行してみます。

  1. 引数文字列を整数に変換
  2. 0 なら終了
  3. それ以外なら、fork を実施
  4. 子プロセスは、引数に文字列 "0" を指定して、自分自身を execve → 即終了する筈
  5. 親プロセスは、子プロセスを wait
  6. 指定整数から 1 を減じて、手順2から繰り返し

繰り返し回数 0x10000 = 約6万4千の実行に要する時間 (単位:秒) は、以下の様な結果でした。

---- MacOSX Linux Solaris
fork + execve 194.90 31.06 68.04

Mac OS X での fork + execve の実行は、Linux と比較して6倍程度の時間を要しています。

Mercurial のテストセット全体では、およそ3万回程度の fork + execve が実施されていますので、上記計測における繰り返し回数 0x10000 = 約6万4千の半分だとすれば、単純計算で (194.90 - 31.06) / 2 = 81.92 秒の所要時間差が生じることになります。

並列度 N でテストを実施することで、見かけ上の性能差を 1/N に減らすことができますが、並列度が 2 〜 4 程度であれば、無視できない差と言えるでしょう。

さて、fork/execve の実行性能を個別に計測した結果と、両者を実際に組み合わせた際の計測結果を比較してみると、以下のようになります。

---- MacOSX Linux Solaris
「fork」実測 27.36 9.35 24.32
「execve」実測 157.19 22.02 39.41
「fork」と「execve」の合算 184.55 31.37 63.73
「fork + execve」実測 194.90 31.06 68.04
「実測」-「合算」(「実測」比) +10.35 (5.3%) -0.31 (1.0%) +4.31 (6.3%)

Linux における "「実測」-「合算」" の -0.31 (実測値の約1%) は、「仮想環境上での簡易計測」ということを考えると、まぁ、見逃しても良さそうな気がしないでもないですが、Mac OS X (と Solaris) における "「実測」-「合算」" は、一応理屈を付けておきたい程度には、無視できない値ですね。

とは言え、底まで調べ上げるには相当に深い沼でしょうから、まずは「差がある」という認識のレベルで止めておくことにします (この性能比較自体、本来の目的からの大幅な脱線ですし……)。

以下、次回に続く。

備考

オンラインマニュアルによると、Linuxfork は、実際には clone システムコールを呼び出すライブラリ関数、という位置付けらしいので、strace 出力等を確認する際には、fork という名前だけに気を取られないように注意が必要です。