モンテカルロ法を使用して PI を計算する小さなプログラムを C で実装しました (主に個人的な関心とトレーニングのため)。基本的なコード構造を実装した後、計算をスレッド化して実行できるようにするコマンドライン オプションを追加しました。
大幅な高速化を期待していましたが、がっかりしました。コマンドラインの概要は明確である必要があります。PI を概算するために行われる反復の最終回数は、コマンドライン経由-iterations
で渡された回数の積です。-threads
空白-threads
のままにすると、デフォルトで1
スレッドになり、メイン スレッドで実行されます。
以下のテストは、合計 8000 万回の反復でテストされています。
Windows 7 64 ビット (Intel Core2Duo マシン) の場合:
Cygwin GCC 4.5.3 を使用してコンパイル:gcc-4 pi.c -o pi.exe -O3
Ubuntu/Linaro 12.04 (8 コア AMD) の場合:
GCC 4.6.3 を使用してコンパイル:gcc pi.c -lm -lpthread -O3 -o pi
パフォーマンス
Windows では、スレッド化されたバージョンは非スレッド化よりも数ミリ秒高速です。正直なところ、もっと良いパフォーマンスを期待していました。Linux では、うーん!一体何?なぜ 2000% も長くかかるのでしょうか? もちろん、これは実装に大きく依存するため、ここで説明します。コマンドライン引数の解析が完了し、計算が開始された後の抜粋:
// Begin computation.
clock_t t_start, t_delta;
double pi = 0;
if (args.threads == 1) {
t_start = clock();
pi = pi_mc(args.iterations);
t_delta = clock() - t_start;
}
else {
pthread_t* threads = malloc(sizeof(pthread_t) * args.threads);
if (!threads) {
return alloc_failed();
}
struct PIThreadData* values = malloc(sizeof(struct PIThreadData) * args.threads);
if (!values) {
free(threads);
return alloc_failed();
}
t_start = clock();
for (i=0; i < args.threads; i++) {
values[i].iterations = args.iterations;
values[i].out = 0.0;
pthread_create(threads + i, NULL, pi_mc_threaded, values + i);
}
for (i=0; i < args.threads; i++) {
pthread_join(threads[i], NULL);
pi += values[i].out;
}
t_delta = clock() - t_start;
free(threads);
threads = NULL;
free(values);
values = NULL;
pi /= (double) args.threads;
}
Whilepi_mc_threaded()
は次のように実装されます。
struct PIThreadData {
int iterations;
double out;
};
void* pi_mc_threaded(void* ptr) {
struct PIThreadData* data = ptr;
data->out = pi_mc(data->iterations);
}
完全なソース コードはhttp://pastebin.com/jptBTgwrにあります。
質問
どうしてこれなの?Linux でこの極端な違いが生じるのはなぜですか? 計算にかかる時間は、少なくとも元の時間の 3/4 になると予想していました。pthread
もちろん、単にライブラリの使い方を誤った可能性もあります。この場合に正しい方法を明確にすることは非常に良いでしょう。