2007年08月15日

並列プログラミング(その2)

はてなブックマークに登録

3.Memory Ordering

シングルプロセッサのマルチスレッドでは、volatile変数をフラグにして簡単な同期を書くことができました。 例えば、次のような感じです。(コンパイラはvolatile変数へのアクセスの順序を入れ替えないものとします)

volatile int done = 0;
volatile struct { int foo; int bar; } foobar;

void writer(void) {
    foobar.foo = fizz();
    foobar.bar = bazz();
    done = 1;
}
 
void reader(void) {
    int foo, bar;

    while (!done) sleep(1);
    foo = foobar.foo;
    bar = foobar.bar;
}

これは、マルチプロセッサ環境では上手くいかないことがあります。今時のCPUは、命令を順番に実行するとは限らないからです。例えば、メモリ書き込みを後回しにしたり、メモリ読み込みを投機的に(先走って)実行します。

こういった順番の入れ替えは、そのプロセッサ単体で見たときにはプログラムの実行に影響が無いようにされていますが、他のプロセッサから見たときにはメモリの更新順序が異なって見えてしまいます。

この問題を解決するために、メモリフェンスという仕組みがあります。例えば、i686では lfence, sfence, mfence という命令があります。lfence命令をプログラムにはさむと、lfence命令より後ろのLoad命令が、lfence命令より先に実行されることがなくなります。sfence命令はlfence命令の逆で、sfence命令より前のStore操作が完了する(キャッシュメモリなどに書き込まれる)のを待ちます。mfence命令はlfenceとsfenceを足したものです。

先ほどのプログラムを、マルチプロセッサでも動くようにメモリフェンスを挿入すると、次のようになります。

volatile int done = 0;
volatile struct { int foo; int bar; } foobar;

void writer(void) {
    foobar.foo = fizz();
    foobar.bar = bazz();
    asm("    sfence;"); // fooとbarの書き込みが確実に実行されるのを待つ.
    done = 1;
}

void reader(void) {
    int foo, bar;

    while (!done) sleep(1);
    asm("    lfence;"); // doneが真になる前に、fooやbarをLoadされるのを防ぐ.
    foo = foobar.foo;
    bar = foobar.bar;
}

もちろん、自分で同期しようとしないで同期APIを呼べば、ちゃんとメモリフェンスもしてくれます。 この領域の問題はデバッガで追えるようなモノではなく、非常にデバッグが難しいので、最初から同期APIを 使用することをお勧めします。

klab_gijutsu2 at 13:00│Comments(0)TrackBack(0)kernel 

トラックバックURL

この記事にコメントする

名前:
URL:
  情報を記憶: 評価: 顔   
 
 
 
Blog内検索
Archives
このブログについて
DSASとは、KLab が構築し運用しているコンテンツサービス用のLinuxベースのインフラです。現在5ヶ所のデータセンタにて構築し、運用していますが、我々はDSASをより使いやすく、より安全に、そしてより省力で運用できることを目指して、日々改良に勤しんでいます。
このブログでは、そんな DSAS で使っている技術の紹介や、実験してみた結果の報告、トラブルに巻き込まれた時の経験談など、広く深く、色々な話題を織りまぜて紹介していきたいと思います。
最新コメント