マルチコアCPUのロック競合による性能劣化はどのようなものか?多くの人が知りたがっていることだと思いますので、マルチコアCPUでのアトミック演算、windowsのCriticalSection、OpenMPのロック演算機能の性能をテストするテストプログラムを書いてみました。
InterlockedIncrementは、アトミック操作のテストに選ばれました。
各ロックとアトミック操作について、サブロックとアンロック操作のシングルタスク実行とマルチタスク実行で消費される時間をテストします。
テストの詳細なコードは以下の通り。
テストマシン環境:Intel 2.66GデュアルコアCPUマシン a
テスト走行の結果は以下の通り:
マルチスレッド、Critical_Section、2,000,000:a = 2000000、time = 5136
シングルスレッド,omp_lock 2,000,000:a = 2000000, time = 052
マルチスレッド,omp_lock 2,000,000:a = 2000000, time = 6013
シングルタスク実行の場合の消費時間は以下の通り:
アトミック動作78ms
Windows CriticalSection 172ms
OpenMPロック動作250ms
したがって、シングルタスクの状況から見ると、アトミック演算が最も速く、WindowsのCriticalSectionが2番目に速く、OpenMPライブラリに付属するロックが最も遅いのですが、これらの演算の時間差はそれほど大きくなく、ロックを使った演算はアトミック演算の2~3倍程度です。
複数のタスクが実行されている場合、消費される時間は以下のようになります:
アトミック動作156ms
Windows CriticalSection 3156ms
OpenMPロック動作1063ms
マルチタスク動作の場合、状況は予想外に変わり、アトミック動作時間はシングルタスク動作の2倍遅く、2つのCPUで動作させた場合、シングルCPUで動作させた場合の2倍遅いというのは想像を絶するもので、これはタスク切り替えのオーバーヘッドが原因と推測されます。
WindowsのCriticalSectionはさらにとんでもない結果で、実際にかかった時間は3,156ms。シングルタスクで実行した場合の18倍以上もかかっており、想像を絶する遅さです。
OpenMPのロック操作は、WindowsのCriticalSectionよりもわずかに優れていましたが、シングルタスクの約7倍である1063msもかかりました。
このことから、マルチコアCPUによるマルチタスク環境では、アトミック演算が最も速く、OpenMPが次に速く、Windows CriticalSectionが最も遅いことがわかります。
また、シングルタスクとマルチタスクでのこれらのロックの性能差からわかるように、マルチコアCPUでのプログラミングは、旧来のシングルコアのマルチタスクプログラミングとは大きく異なります。
それは、このテストは極端なケースのテストであることに注意する必要があります、ロック操作は、単純なプラス1操作であり、ロック競合の数は200万回として、実際の状況では、タスクに起因する実行中にロックする必要がない他の多くのコードがあり、実際の状況でのパフォーマンスは、このテストのパフォーマンスよりもはるかに良くなります。
テストコードは以下の通り:
// TestLock.cpp : OpenMPタスクの原子操作とロックのパフォーマンステスト手順。
//
#include <windows.h>
#include <time.h>
#include <process.h>
#include <omp.h>
#include <stdio.h>
void TestAtomic()
{
clock_t t1,t2;
int i = 0;
volatile LONG a = 0;
t1 = clock();
for( i = 0; i < 2000000; i++ )
{
InterlockedIncrement( &a);
}
t2 = clock();
printf("SingleThread, InterlockedIncrement 2,000,000: a = %ld, time = %ld/n", a, t2-t1);
t1 = clock();
#pragma omp parallel for
for( i = 0; i < 2000000; i++ )
{
InterlockedIncrement( &a);
}
t2 = clock();
printf("MultiThread, InterlockedIncrement 2,000,000: a = %ld, time = %ld/n", a, t2-t1);
}
void TestOmpLock()
{
clock_t t1,t2;
int i;
int a = 0;
omp_lock_t mylock;
omp_init_lock(&mylock);
t1 = clock();
for( i = 0; i < 2000000; i++ )
{
omp_set_lock(&mylock);
a+=1;
omp_unset_lock(&mylock);
}
t2 = clock();
printf("SingleThread,omp_lock 2,000,000:a = %ld, time = %ld/n", a, t2-t1);
t1 = clock();
#pragma omp parallel for
for( i = 0; i < 2000000; i++ )
{
omp_set_lock(&mylock);
a+=1;
omp_unset_lock(&mylock);
}
t2 = clock();
printf("MultiThread,omp_lock 2,000,000:a = %ld, time = %ld/n", a, t2-t1);
omp_destroy_lock(&mylock);
}
void TestCriticalSection()
{
clock_t t1,t2;
int i;
int a = 0;
CRITICAL_SECTION cs;
InitializeCriticalSection(&cs);
t1 = clock();
for( i = 0; i < 2000000; i++ )
{
EnterCriticalSection(&cs);
a+=1;
LeaveCriticalSection(&cs);
}
t2 = clock();
printf("SingleThread, Critical_Section 2,000,000:a = %ld, time = %ld/n", a, t2-t1);
t1 = clock();
#pragma omp parallel for
for( i = 0; i < 2000000; i++ )
{
EnterCriticalSection(&cs);
a+=1;
LeaveCriticalSection(&cs);
}
t2 = clock();
printf("MultiThread, Critical_Section, 2,000,000:a = %ld, time = %ld/n", a, t2-t1);
DeleteCriticalSection(&cs);
}
int main(int argc, char* argv[])
{
TestAtomic();
TestCriticalSection();
TestOmpLock();
return 0;
}





