fork VS vfork VS pthread
なんとなくベンチマーク取ってみた。コードはどれも
- 子プロセス(別スレッド)を作成
- 親は子を待つ(waitpid/pthread_join)
- 子は何もせずにexit(pthread_exit)する。
これだけだけど、少しずつ違うパターンで測定してみた。1つはグローバル変数がまったく無い状態。もう1つはグローバル変数があるけど、使用されない状態。最後はグローバル変数があり、親も子(プロセス&スレッド)もそれに書き込む場合。グローバル変数というのは
#if defined DEC_GLOBAL_VAR || defined USE_GLOBAL_VAR long long buf1[4098*2]; long long buf2[4098*2]; long long buf3[4098*2]; long long buf4[4098*2]; #endif
このようなもので、それを使用するというのは
buf1[0] = buf2[0] = buf3[0] = buf4[0] = 1;
というもの。forkに関して言えば下のような感じになる。
void do_fork(void) { pid_t pid; if ( (pid = fork()) > 0) { #if defined USE_GLOBAL_VAR buf1[0] = buf2[0] = buf3[0] = buf4[0] = 1; #endif if (waitpid(pid, NULL, 0) <= 0) syserr("waitpid"); } else if (pid == 0) { #if defined USE_GLOBAL_VAR buf1[0] = buf2[0] = buf3[0] = buf4[0] = 2; #endif exit(EXIT_SUCCESS); } else { syserr("fork"); } }
結果は
fork | vfork | pthread | |
グローバル変数無し | 0.942 | 0.108 | 0.044 |
グローバル変数の宣言のみ | 1.080 | 0.112 | 0.050 |
グローバル変数に書き込み | 1.214 | 0.100 | 0.060 |
まぁ、大部分は予想通りでしょう。速度的にはpthread > vfork > forkです。vforkは普通は使わないですけど、ここぞという場面でforkのかわりに使えばかなり高速になるでしょう(ただし、man vforkは良く読まないとダメ、forkとは性質が違うし、そもそも推奨されてない)。
それよりもグローバル変数を定義したり、それを使用したときの違いのほうが興味深い。fork意外ではほとんどグローバル変数の影響を受けていないことが分かると思う。これはvforkとpthreadはプロセスのデータ空間をコピーしないので、基本的にいくらメモリを使用していても速度に影響しない(はず)。それに対してforkはこれをコピーするから遅くなる。そして最近のforkの実装ではcopy-on-write(後述)という手法を用いてデータ空間のコピーを行なうから、データに対して書き込みが無い限りはデータがコピーされない(はず)なので、グローバル変数を使用するかどうかかで速度に大きな違いがでるというわけ。
copy-on-writeってのは検索してもらえば分かると思うけど、要はプロセス空間のコピーを最初に全部やるんじゃなくて、必要なとき(空間に書きこむとき)にコピーするということ。遅延効果ってやつだね。
あぁ、clone()も実験すれば良かったかなぁ。多分pthreadと同じような結果になると思うんだけど(スレッドの内部は普通clone()で実装される)。急にpthreadのソースが見たくなったな。ちょっと見て来るか。