昆布大好き!

主にプログラミングの技メモ

偽共有 マルチスレッドの敵

マルチコアプロセッサの複数のコアが並走しても、キャッシュが共有であれば上手く並走しないことがあるようです。

あるスレッドがアクセス中のキャッシュラインを、別のスレッドが書き換えた場合、メモリの不整合が生じないようにキャッシュのデータをメモリに書き戻す、とのことです。

手っ取り早い話、マルチスレッドで、同じか近いメモリエリアにアクセスすると遅くなりますよ、ということです。

ただ、ずいぶんと捕らえ難い現象のようです。 とりあえず、再現してみました。

#include <iostream>
#include <thread>
#include <ctime>

unsigned char ary[1000];
const int N = 100000000;

void proc(int s)
{
    for ( int i=0; i<N; i++ ) {
        // インクリメントしてメモリを更新
        ary[s]++;
    }
}

int main()
{
    clock_t  s, e;

    // 隣のメモリを更新
    s = clock();
    std::thread  thr1(proc, 0), thr2(proc, 1);
    thr2.join();
    thr1.join();
    e = clock();
    std::cout << (e - s) << std::endl;

    // 8バイト先のメモリを更新
    s = clock();
    thr1 = std::thread(proc, 0), thr2 = std::thread(proc, 8);
    thr2.join();
    thr1.join();
    e = clock();
    std::cout << (e - s) << std::endl;

    // 16バイト先
    s = clock();
    thr1 = std::thread(proc, 0), thr2 = std::thread(proc, 16);
    thr2.join();
    thr1.join();
    e = clock();
    std::cout << (e - s) << std::endl;

    // 32バイト先
    s = clock();
    thr1 = std::thread(proc, 0), thr2 = std::thread(proc, 32);
    thr2.join();
    thr1.join();
    e = clock();
    std::cout << (e - s) << std::endl;

    // 64バイト先
    s = clock();
     thr1 = std::thread(proc, 0), thr2 = std::thread(proc, 64);
    thr2.join();
    thr1.join();
    e = clock();
    std::cout << (e - s) << std::endl;

    return 0;
}

と、近いエリアから少しずつ離れたエリアのメモリを更新するようにしてみました。 CPUはCore2 Quad Q5550、OSはWindows 8 64bitでした。 結果はこう。

アクセス 時間[clock]
1バイト 567
8バイト 614
16バイト 531
32バイト 213
64バイト 213

32バイトくらいで差が出てきますね。 ただこれ最適化していない場合の結果で、したコードだと結果が違ってきます。 最適化して実行した場合、この程度の雑な処理ではclcokの値が0近くになるので計れません。ぐぬぬ

上のように、アドレスを正確に把握出来る場合はまだ良いですが、当然と言えば当然、こちらの記事.NET の問題 : 偽共有 のように、マネージドコードでも偽共有は起きます。

オブジェクトがヒープのどこに展開されるかなど知るわけありません。ますますもって度しがたいです。