OpenMPでの最適なスレッド数

コア数に依存する数値があると思って調べたけど分からなかったので計測した。

環境

CPU
Intel Core 2 Duo T7200 @2.2GHz 2.2GHz
RAM
1GB x 2
コンパイラ
VC++ 2008 Expressについているもの
OpenMP
Windows SDKを入れたら使えた

コード

ひたすら素数を数えるもの。動いておけばいいので最適化とかはあまり考えていない。
まず並列化なし測定して、次にスレッド数を2, 4, 8...64と増やしながら計測する。128には設定できなかったので64まで。

#include <stdio.h>
#include <windows.h>
#include <omp.h>

#define LOOP 5
#define TEST 0xffff
#define FILEOUTPUT 0

//    return m == 1 ? 1:(n % m == 0 ? 0: is_prime(n, m - 1));
int is_prime(int n)
{
    int m;
    for (m = n - 1; m > 1; --m) {
        if (n % m == 0) {
            return 0;
        }
    }
    
    return 1;
}

int main(void)
{
    int n, s, e, t, i;
    int c = 0;
#if FILEOUTPUT
    FILE *fp;
#endif

    // 事前に少し動かしておく
    s = GetTickCount();
    for (n = 3; n < TEST; ++n) {
        if (is_prime(n)) {
            ++c;
        }
    }
    e = GetTickCount();
    
    // 標準計測
    for (i = 0; i < LOOP; ++i) {
#if FILEOUTPUT
        fp = fopen("./test", "w");
#endif
        c = 0;
        s = GetTickCount();
        for (n = 3; n < TEST; ++n) {
            if (is_prime(n)) {
                ++c;
#if FILEOUTPUT
                fprintf(fp, "%d\n", n);
                fflush(fp);
#endif
            }
        }
        e = GetTickCount();
        printf("S   : %d (%d)\n", e - s, c);
#if FILEOUTPUT
        fclose(fp);
#endif
    }
    for (t = 2; t <= 64; t *= 2) {
        // OpenMP計測
#ifdef _OPENMP
        omp_set_num_threads(t);
#endif
        for (i = 0; i < LOOP; ++i) {
#if FILEOUTPUT
            fp = fopen("./test", "w");
#endif
            c = 0;
            s = GetTickCount();
    
#ifdef _OPENMP
#pragma omp parallel for reduction(+:c)
#endif
            for (n = 3; n < TEST; ++n) {
//                if (omp_get_num_threads() != t) {
//                    fprintf(stderr, "illegal threads %d,%d\n", t, omp_get_num_threads());
//                }
                if (is_prime(n)) {
                    ++c;
#if FILEOUTPUT
                    fprintf(fp, "%d\n", n);
                    fflush(fp);
#endif
                }
            }
            e = GetTickCount();

            printf("M%-3d: %d (%d)\n", t, e - s, c);
#if FILEOUTPUT
            fclose(fp);
#endif
        }
    }
    getchar();

    return 0;
}

個人的予想では8が一番速い。

結果

ファイル出力はあってもなくてもそう変わらなかった。ディスクキャッシュ?
ファイル出力ありの結果。

// シングル/マルチ[スレッド数]: 完了時間ms: (素数数 @並列化がバグっていると変な値になる)
S   : 7862 (6541)
S   : 7863 (6541)
S   : 7956 (6541)
S   : 7878 (6541)
S   : 7925 (6541)
M2  : 6271 (6541)
M2  : 6240 (6541)
M2  : 6224 (6541)
M2  : 6225 (6541)
M2  : 6240 (6541)
M4  : 5522 (6541)
M4  : 5367 (6541)
M4  : 5304 (6541)
M4  : 5117 (6541)
M4  : 5631 (6541)
M8  : 4680 (6541)
M8  : 4696 (6541)
M8  : 4508 (6541)
M8  : 4353 (6541)
M8  : 4726 (6541)
M16 : 4431 (6541)
M16 : 4508 (6541)
M16 : 4493 (6541)
M16 : 4461 (6541)
M16 : 4181 (6541)
M32 : 4399 (6541)
M32 : 4259 (6541)
M32 : 4275 (6541)
M32 : 4227 (6541)
M32 : 4244 (6541)
M64 : 4228 (6541)
M64 : 4259 (6541)
M64 : 4243 (6541)
M64 : 4196 (6541)
M64 : 4525 (6541)

32で頭打ちな感じ。

感想

スレッド数が増えれば増えるほど速くなるのは、Windowsのスケジューラーがスレッド単位でまわしているから、スレッドが多いほうが得られる実行時間が長いとか? うー、ハードのことは分からない。最適なスレッド > コア数ということは分かった。

けつろん

あまり納得のできる結果は得られなかったけど、16くらいにする。実行中に変数の同期を取るタイミングあったりするとまたややこしいことになりそう。Intelの意見が聞きたい。
とりあえず#pragma ompと書くだけで2倍の性能になったので、OpenMPすげー、実行速度を気にするならまず並列化しろー、と思いました。

このテストとは関係ないけど

再起で書くとスタックオーバーフローした。
コンパイラがこうだから、再起関数は使えないという認識がある。