MacOS Xのfork/execが遅い件について
オフトピ*1だが、「libtaskとPthreadの比較」で軽く触れた、MacOS Xのforkが遅い件についてもう少し調べてみた。
前回使ったプログラムはこんな感じのもの。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/time.h> enum { ITER = 100 }; void do_fork(void) { pid_t pid; switch (pid = fork()) { case -1: perror("fork"); exit(1); case 0: /* child */ exit(1); default: while (wait(0) != pid); } } int main(int argc, char **argv) { int i; struct timeval tv; double begin, end; gettimeofday(&tv, NULL); begin = tv.tv_sec + tv.tv_usec * 1e-6; for (i = 0; i < ITER; i++) { do_fork(); } gettimeofday(&tv, NULL); end = tv.tv_sec + tv.tv_usec * 1e-6; printf("%g\n", (end - begin) / ITER); return 0; }
今回は、BYTE UNIX Benchmark 4.1.0のシステムベンチマークで比較してみた。
MacOS X 10.4、Core 2 Duo/2.0GHzの場合。
TEST BASELINE RESULT INDEX Execl Throughput 188.3 435.8 23.1 File Copy 1024 bufsize 2000 maxblocks 2672.0 48484.0 181.5 File Copy 256 bufsize 500 maxblocks 1077.0 13844.0 128.5 File Read 4096 bufsize 8000 maxblocks 15382.0 315259.0 205.0 Pipe Throughput 111814.6 439237.6 39.3 Pipe-based Context Switching 15448.6 107754.3 69.8 Process Creation 569.3 887.4 15.6 Shell Scripts (8 concurrent) 44.8 270.5 60.4 System Call Overhead 114433.5 277117.6 24.2 ========= FINAL SCORE 57.5
Linux CentOS 5.2、Pentium 4/3.0GHz dualの場合。
TEST BASELINE RESULT INDEX Execl Throughput 188.3 3692.7 196.1 File Copy 1024 bufsize 2000 maxblocks 2672.0 59336.0 222.1 File Copy 256 bufsize 500 maxblocks 1077.0 16637.0 154.5 File Read 4096 bufsize 8000 maxblocks 15382.0 619832.0 403.0 Pipe Throughput 111814.6 538893.8 48.2 Pipe-based Context Switching 15448.6 134891.3 87.3 Process Creation 569.3 11984.3 210.5 Shell Scripts (8 concurrent) 44.8 672.0 150.0 System Call Overhead 114433.5 592053.3 51.7 ========= FINAL SCORE 138.4
やはりexeclスループットで8.3倍、プロセス生成で13.5倍も性能差がある。ハードウェアが全く違うので、一概に比較できないが、システムコール自体のオーバヘッドが2.1倍なので、fork/execは明らかに遅いといえる。
ということで、ktraceを使って実行時間をトレースしてみる。
$ ktrace ./bench.fork $ kdump -R -f ktrace.out
wait4を呼出してから戻るまで(ktraceのオーバヘッドで遅くなっているが)3ミリ秒近くかかっている。これはwait4に処理時間を食っているわけではなく、この間に子プロセスが動いているのだろうが、なぜこんなに時間がかかるのか。やっぱりよくわからない。
9775 bench.fork 0.000442 CALL fork 9775 bench.fork 0.000119 RET fork 9776/0x2630 9775 bench.fork 0.000119 CALL wait4(0xffffffff,0,0,0) 9775 bench.fork 0.003081 RET wait4 9776/0x2630
じゃぁ、他のプロセスの影響を極力なくそうと、シングルユーザモード*2で測定してみた。execlスループットは435.8 lpsから828.0 lps、プロセス生成は887.4 lpsから1856.8 lpsと、2倍前後改善した。それでも遅いなぁ。