質問
誤った共有を実証するための次のタスクがあり、簡単なプログラムを書きました。
#include <sys/times.h>
#include <time.h>
#include <stdio.h>
#include <pthread.h>
long long int tmsBegin1,tmsEnd1,tmsBegin2,tmsEnd2,tmsBegin3,tmsEnd3;
int array[100];
void *heavy_loop(void *param) {
int index = *((int*)param);
int i;
for (i = 0; i < 100000000; i++)
array[index]+=3;
}
int main(int argc, char *argv[]) {
int first_elem = 0;
int bad_elem = 1;
int good_elem = 32;
long long time1;
long long time2;
long long time3;
pthread_t thread_1;
pthread_t thread_2;
tmsBegin3 = clock();
heavy_loop((void*)&first_elem);
heavy_loop((void*)&bad_elem);
tmsEnd3 = clock();
tmsBegin1 = clock();
pthread_create(&thread_1, NULL, heavy_loop, (void*)&first_elem);
pthread_create(&thread_2, NULL, heavy_loop, (void*)&bad_elem);
pthread_join(thread_1, NULL);
pthread_join(thread_2, NULL);
tmsEnd1 = clock();
tmsBegin2 = clock();
pthread_create(&thread_1, NULL, heavy_loop, (void*)&first_elem);
pthread_create(&thread_2, NULL, heavy_loop, (void*)&good_elem);
pthread_join(thread_1, NULL);
pthread_join(thread_2, NULL);
tmsEnd2 = clock();
printf("%d %d %d\n", array[first_elem],array[bad_elem],array[good_elem]);
time1 = (tmsEnd1-tmsBegin1)*1000/CLOCKS_PER_SEC;
time2 = (tmsEnd2-tmsBegin2)*1000/CLOCKS_PER_SEC;
time3 = (tmsEnd3-tmsBegin3)*1000/CLOCKS_PER_SEC;
printf("%lld ms\n", time1);
printf("%lld ms\n", time2);
printf("%lld ms\n", time3);
return 0;
}
結果を見たとき、私は非常に驚きました(i5-430mプロセッサで実行します)。
- 誤った共有では、1020ミリ秒でした。
- 誤った共有がなければ、710ミリ秒で、300%ではなく30%高速でした(一部のサイトでは、300〜400%よりも速くなると書かれていました)。
- pthreadsを使用せずに、580ミリ秒でした。
私の間違いを見せて、なぜそれが起こるのかを説明してください。
解決
c -clock()関数の概要 - 最初から最後まで経過したCPUクロックの数が得られます。したがって、2つの並列スレッドを実行すると、CPUサイクルの数はCPU1 + CPU2のクロックサイクルのクロックサイクルになります。あなたが望むのは本当のタイマー時計だと思います。この場合、clock_gettime()を使用すると、予想される出力を取得する必要があります。
clock_gettime()でコードを実行しました。これを手に入れました。
誤った共有874.587381ミリ秒
誤った共有なし331.844278ミリ秒
シーケンシャル計算604.160276ミリ秒
他のヒント
誤った共有は、同じ物理メモリの領域にアクセスする個別のキャッシュを持つ複数のコアの結果です(同じアドレスではありませんが、それは真の共有になります)。
誤った共有を理解するには、キャッシュを理解する必要があります。ほとんどのプロセッサでは、各コアには独自のL1キャッシュがあり、最近アクセスされたデータが保持されます。キャッシュは「ライン」で編成されており、通常は32バイトまたは64バイトのデータのチャンクが揃っています(プロセッサに応じて)。キャッシュにないアドレスから読むと、ライン全体がメインメモリ(またはL2キャッシュ)からL1に読み取られます。キャッシュ内のアドレスに書き込むと、そのアドレスを含む行に「汚れ」とマークされます。
共有の側面が登場します。複数のコアが同じ行から読み取られている場合、それぞれがL1にラインのコピーを持つことができます。ただし、コピーに汚れたマークが付いている場合、他のキャッシュのラインを無効にします。これが発生しなかった場合、1つのコアに作成された書き込みは、かなり後まで他のコアに表示されない可能性があります。そのため、次回他のコアがその行から読みに行くとき、キャッシュは見逃し、再びラインを取得する必要があります。
間違い 共有は、コアが同じ行のさまざまなアドレスに読み書きしているときに発生します。彼らはデータを共有していませんが、キャッシュは彼らがとても近いので、彼らがそうであるように行動します。
この効果は、プロセッサのアーキテクチャに大きく依存しています。単一のコアプロセッサがある場合、共有がないため、効果はまったく表示されません。キャッシュラインが長い場合、「悪い」ケースと「良い」ケースの両方で、それらがまだ近くにあるため、効果が表示されます。コアがL2キャッシュを共有していない場合(私はそうしていると思います)、あなたが言ったように300〜400%の違いが見られるかもしれません。
また、各スレッドが読み取りと書き込みの両方であることが重要であることを知りたいかもしれません(+=の代わりに)。一部のプロセッサにはあります 書き込みスルー キャッシュは、コアがキャッシュ内ではなくアドレスに書き込む場合、メモリからラインを見逃してフェッチすることはありません。これとは対照的です 返事を書く キャッシュ。