2015-01-13 3 views
5

フォーク後にtzset()を呼び出すのが非常に遅いようです。フォーク前に親プロセスで最初にtzset()を呼び出すと、遅いことがわかります。私のTZ環境変数が設定されていません。私はdtruss私のテストプログラムとそれは子プロセスがすべてのtzset()の呼び出しのために読むことを明らかにしたが、親プロセスは一度だけそれを読む。このファイルアクセスは遅さの原因と思われますが、子プロセスで毎回アクセスする理由を特定できませんでした。ここでMac OS Xでフォークした後にtzset()が遅くなるのはなぜですか?

は私のテストプログラムfoo.cのである:

#include <stdio.h> 
#include <stdlib.h> 
#include <sys/time.h> 
#include <unistd.h> 

void check(char *msg); 

int main(int argc, char **argv) { 
    check("before"); 

    pid_t c = fork(); 
    if (c == 0) { 
    check("fork"); 
    exit(0); 
    } 

    wait(NULL); 

    check("after"); 
} 

void check(char *msg) { 
    struct timeval tv; 

    gettimeofday(&tv, NULL); 
    time_t start = tv.tv_sec; 
    suseconds_t mstart = tv.tv_usec; 

    for (int i = 0; i < 10000; i++) { 
    tzset(); 
    } 

    gettimeofday(&tv, NULL); 
    double delta = (double)(tv.tv_sec - start); 
    delta += (double)(tv.tv_usec - mstart)/1000000.0; 

    printf("%s took: %fs\n", msg, delta); 
} 

私がコンパイルされ、このようにfoo.cを実行:

[[email protected] scratch]$ clang -o foo foo.c 
[[email protected] scratch]$ env -i ./foo 
before took: 0.002135s 
fork took: 1.122254s 
after took: 0.001120s 

私も再現する(Mac OS X 10.10.1を実行していますよ10.9.5)。

私はもともとルビー経由の遅さに気付きました(子プロセスでは時間#ローカルタイムが遅い)。

+0

マイナー:Dではなく( ''よりも(開始、tv.tv_sec) 'difftimeをお勧めします可)(tv.tv_sec - start) 'となります。 'delta + =(double)...'の '(double)'は必要ありません。 – chux

+0

パフォーマンスが悪い主な原因は、タイムゾーンファイルが** ** localtimeの呼び出しごとに変更されていないことを実際にチェックしていることです。システム設定がその下から変更できるように動作するのです。悪いフォークの振る舞いは、fork()で正しく機能しないファイル変更通知メカニズムの副作用である可能性があります。これは、コードを実行して楽器を動かすことに基づいていると仮定しているだけです。 – Petesh

+0

私は通知理論が好きなので、もう少し調査し、以下の答えを掲載しました。 – Muir

答えて

3

nasal demonsをお見逃しなく!

POSIXは、fork()の後、およびexec*()関数の呼び出しの前に、非同期シグナルセーフな関数だけが子プロセスを呼び出すことができると述べています。 standard(強調追加)から:

...子プロセスのみexec関数のいずれかが呼び出されるような時間まで、非同期シグナルセーフオペレーションを実行してもよいです。

...

POSIXのプログラマはfork()を呼ぶ理由は2つあります。 1つの理由は、 であり、同じプログラム(新しいプロセスを作成することによって元々 はPOSIXでのみ可能でした)内に新しい制御スレッドを作成することです。 他は、別のプログラムを実行している新しいプロセスを作成することです。 後者の場合、fork()へのコールの直後に、 execのいずれかの関数が呼び出されます。

fork()をマルチスレッドワールドで動作させる際の一般的な問題 は、すべてのスレッドで何をすべきかです。 2つの選択肢があります。 1つの は、すべてのスレッドを新しいプロセスにコピーすることです。これにより、 プログラマまたは実装は、システムコール時に 中断されたスレッド、または新しいプロセスで が実行されないシステムコールを実行しようとしているスレッドを処理します。もう1つの選択肢は fork()を呼び出すスレッドのみをコピーすることです。これにより、プロセスローカルリソースの状態が通常、プロセス のメモリに保持されるという難点が になります。 fork()を呼び出していないスレッドがリソースを保持している場合、子プロセスにリソース リソースが解放されているスレッド が存在しないため、リソース は解放されません。プログラマは、マルチスレッドプログラムを書いている場合

、最初 同じプログラムに新しいスレッドを作成し、fork()の使用を記載し、pthread_create()機能によって提供 です。 fork()機能は、このように、新たなプログラムを実行するためにのみ使用 、およびexec機能にfork()への呼び出しと呼び出し の間に一定のリソースを必要とする関数を呼び出す の効果である定義されていません。

非同期シグナルセーフ機能herehereのリストがあります。他の関数については、展開しているプラ​​ットフォームの実装が標準以外の安全保証を追加していることが具体的に文書化されていない場合は、安全でないとみなし、fork()の子側の動作を未定義とみなす必要があります。

+0

"...子プロセスは、exec関数の1つが"マルチスレッドプロセスがfork() "を呼び出すときに何が起きるかを参照していると思われるまで、非同期シグナル安全な操作のみを実行できます。だから、私はそれが上記の質問に当てはまるとは思わない。 – psanford

+0

それは根拠ですが、私はルールが条件付きだとは思わない。どのプログラムもマルチスレッドになる可能性があるため、システムライブラリはそれを考慮する必要があります。'pthread_atfork()'コールバックを登録することができます。このコールバックの実装では、fork時にプログラムがマルチスレッドになることが想定されます。したがって、それらは一時的に共有状態を準備コールバックに「フォークセーフ」状態にし、親コールバックで復元します(子コールバックで復元する必要はないため)。 –

+0

これはあまりにも幅広い読書だと思います。 APUE、Linuxプログラミング環境、Butenhofのどちらもマルチスレッドプログラム以外ではこの制限について言及していません。 @psanfordが指摘しているように、この標準は "マルチスレッドプロセスがfork()"を呼び出す場合、これを明確に分離しています。私は、スレッドの準備ができていないシステムライブラリを、そのような場合に問題が発生する範囲が実際には作成されていないため、説明する方法は壊れてしまいます。 –

6

Ken Thomasesの応答は正しいかもしれませんが、私はまだforkの後にこのような単純/共通操作を実行するシングルスレッドプログラムの予期しない動作が遅いことを発見したので、より具体的な回答が不思議でした。 http://opensource.apple.com/source/Libc/Libc-997.1.1/stdtime/FreeBSD/localtime.c(これは正しいソースではないことを100%は確信していません)を調べた後、私は答えがあると思います。

このコードでは、パッシブ通知を使用して、タイムゾーンが変更されたかどうかを確認しています(statは毎回/etc/localtimeとなります)。 forkの後、子プロセスで登録された通知トークンが無効になったようです。さらに、このコードでは、タイムゾーンが変更されたという肯定的な通知として、無効なトークンを使用しないようにエラーを処理し、毎回/etc/localtimeを読み込みます。私はforkの後に得ることができる未定義の行動のようなものだと思いますか?しかし、ライブラリがエラーに気付き、通知のために再登録した方がいいでしょう。ここ

ステータス値と誤差値を混合localtime.cからのコードの抜粋である:

nstat = notify_check(p->token, &ncheck); 
if (nstat || ncheck) { 

I登録トークンは、このプログラムを使用してフォーク後に無効となることを実証した:

#include <notify.h> 
#include <stdio.h> 
#include <stdlib.h> 

void bail(char *msg) { 
    printf("Error: %s\n", msg); 
    exit(1); 
} 

int main(int argc, char **argv) { 
    int token, something_changed, ret; 

    notify_register_check("com.apple.system.timezone", &token); 

    ret = notify_check(token, &something_changed); 
    if (ret) 
    bail("notify_check #1 failed"); 
    if (!something_changed) 
    bail("expected change on first call"); 

    ret = notify_check(token, &something_changed); 
    if (ret) 
    bail("notify_check #2 failed"); 
    if (something_changed) 
    bail("expected no change"); 

    pid_t c = fork(); 
    if (c == 0) { 
    ret = notify_check(token, &something_changed); 
    if (ret) { 
     if (ret == NOTIFY_STATUS_INVALID_TOKEN) 
     printf("ret is invalid token\n"); 

     if (!notify_is_valid_token(token)) 
     printf("token is not valid\n"); 

     bail("notify_check in fork failed"); 
    } 

    if (something_changed) 
     bail("expected not changed"); 

    exit(0); 
    } 

    wait(NULL); 
} 

そして、このようにそれを実行しました:

muir-mb:projects muir$ clang -o notify_test notify_test.c 
muir-mb:projects muir$ ./notify_test 
ret is invalid token 
token is not valid 
Error: notify_check in fork failed 
関連する問題