Share via


Xbox 360 および Microsoft Windows における複数コアのコーディング

ゲーム ディベロッパー グループ、ソフトウェア デザイン エンジニア

Bruce Dawson 著

2006 年 8 月

はじめに

長年、プロセッサのパフォーマンスは順調に向上し続けてきたため、ゲームなどのプログラム側では特別なことをしなくても、増大を続けるプロセッサのパワーを享受できました。

この傾向は変わりました。現在、シングル プロセッサ コアのパフォーマンスは向上したとしてもごくわずかしか改善されなくなりました。しかし、一般的なコンピューターやゲーム機の処理能力は向上を続けています。従来との違いは、このようなパフォーマンス向上がほとんどの場合 1 台のマシンに複数のプロセッサ コア (多くは 1 チップ) を搭載することで実現されていることです。Xbox 360 CPU では、1 チップに 3 つのプロセッサ コアを備えています。そして 2006 年中に販売される PC プロセッサの約 70% がマルチコアになる見込みです。

処理能力は従来と同じくらい大幅な伸び率で上昇していますが、この能力を利用するためには、マルチスレッド コードを作成する必要があります。マルチスレッド プログラミングでは、設計時とプログラミング時にこれまでにない困難が生じます。このドキュメントでは、マルチスレッド プログラミングを始めるにあたってのヒントを解説します。

適切な設計の重要性

マルチスレッド プログラムを適切に設計することは重要ですが、それが非常に困難な場合もあります。主要なゲーム システムを無計画に別々のスレッドに移動すると、各スレッドがほとんどの時間を他のスレッドの処理待ち時間に費やすことになるでしょう。このような設計では、複雑性が増大してデバッグが非常に困難になるだけでなく、パフォーマンスの向上もほとんど見込めません。

スレッドで同期やデータ共有が必要になるたびに、データ破損、同期オーバーヘッド、デッドロック、複雑性が発生する可能性があります。したがって、マルチスレッド設計ではすべての同期ポイントと通信ポイントを明確に記述するとともに、そのようなポイントを可能な限り減らす必要があります。スレッド間での通信が必要な部分では、コーディングの手間が増大するため、大量のソース コードに影響する場合は生産性の低下をまねきます。

マルチスレッドを設計する上で最も単純な目標は、コードを大きな独立した部分に分割することです。次に、その部分どうしの通信を 1 フレーム内で数回だけに制限することで、複雑性を抑えながら、マルチスレッドによる大幅な速度向上が実現されます。

一般的なスレッド タスク

一部の種類のタスクは、独立したスレッドに渡す方が適していると実証されています。次の一覧はそのすべてを示しているわけではありませんが、参考になるでしょう。

レンダリング

レンダリングでは、シーン グラフのウォーキングを行う場合もあれば、D3D 機能の呼び出しのみを行う場合もあります。CPU 時間の 50% 以上を占めることもよくあります。そのため、レンダリングを他のスレッドに移動することには大きな利点があります。更新スレッドで何らかのレンダリング記述バッファーに入力した後、それをレンダリング スレッドで処理できます。

ゲーム更新スレッドはレンダリング スレッドよりも常に 1 フレーム先行しています。つまり、ユーザーの操作が画面に反映されるまでに 2 フレームかかります。こうした遅延の増大が問題になる可能性もありますが、負荷を分散することでフレーム レートが向上するため、通常は全体的な遅延は許容できる程度に収まります。

ほとんどの場合、依然としてレンダリングはすべて 1 つのスレッドで実行されますが、これはゲーム更新スレッドとは別のスレッドです。

D3DCREATE_MULTITHREADED フラグを使用して、あるスレッドでレンダリングを行い、他のスレッドでリソースの作成を行っている場合がありますが、このフラグは Xbox 360 では無視されるので、Windows では使用しないでください。Windows でこのフラグを指定すると、D3D での同期処理にかなりの時間が費やされるため、レンダリング スレッドの速度が低下します。

ファイルの圧縮解除

読み込み時間は常に長くなるものです。フレーム レートに影響を及ぼすことなくメモリーにデータをストリーミングするには困難が伴います。ディスク上のすべてのデータが積極的に圧縮されていれば、ハード ドライブや光学ディスクからのデータ転送速度が制限要因となる可能性は少なくなります。通常、シングルスレッド プロセッサでは、読み込み時間を短縮するための圧縮解除に使用できるプロセッサ時間は十分に確保できません。しかしマルチプロセッサ システムでは、無駄になるはずの CPU サイクルがファイルの圧縮解除に利用されます。これにより、読み込み時間とストリーミングが改善され、ディスクの容量も節約できます。

制作段階で実行する必要のある処理の代替として、ファイルの圧縮解除を利用しないでください。たとえば、レベル ローディングの際に XML データを解析するためだけに 1 つのスレッドを使用するのでは、マルチスレッドを利用してプレイヤー体験を向上していることになりません。

ファイル圧縮解除スレッドを使用する際にも、データの読み取り効率を最大限に高めるために、非同期ファイル I/O と大きなリードを使用する必要があります。

グラフィックス フラフ

それほど重要でなくても、ゲームの表現を向上する微細なグラフィックは数多くあります。これには、プロシージャ生成される雲のアニメーション、布地や毛髪のシミュレーション、プロシージャ生成される波、プロシージャ生成される植物、追加パーティクル、ゲーム プレイと無関係な物理計算などが含まれます。

このようなエフェクトはゲーム プレイに影響しないため、注意を要する同期問題は生じません。せいぜい 1 フレームに 1 回ほどしか他のスレッドと同期しません。さらに、Windows 向けゲームの場合、マルチコア CPU マシンを利用するゲーマーに対してはこのようなエフェクトによって価値を高める一方、シングルコア マシンでは自動的にエフェクトを省略することができるため、さまざまな機能を幅広く用意することができます。

物理計算

通常、ゲーム更新では物理計算の結果が直ちに必要となるため、ゲーム更新スレッドと並行して動作する独立スレッドで物理計算を処理することはできません。物理計算のマルチスレッド化の代わりとなるのが、複数のプロセッサで実行する方法です。この処理は可能ですが、共有データ構造体へのアクセスを頻繁に必要とする複雑なタスクになります。物理計算の負荷をメイン スレッドだけで実行できるほど低く抑えられれば、設計は簡単になります。

複数のスレッドにおける物理計算の実行をサポートするライブラリを使用できます。ただし、これを使用すると問題が発生する場合があります。ゲームで物理計算を実行する際には多数のスレッドを使用しますが、それ以外の時間にはほとんどスレッドを使用しません。複数のスレッドで物理計算を実行するには、フレーム全体で均等に負荷を分散するために、アドレッシングが必要です。マルチスレッド物理計算エンジンを作成する場合、データ構造体、同期ポイント、負荷分散のすべてに十分注意を払う必要があります。

マルチスレッド設計の例

Windows 向けゲームの場合、CPU コアの数が異なるマシンで実行できる必要があります。2 コアのマシンも急速に普及しつつありますが、まだほとんどのゲーム マシンではコアは 1 つのみです。一般的な Windows 向けゲームでは、更新とレンダリングの負荷を 1 つのスレッドに割り当て、それ以外の機能の負荷をオプションのワーカー スレッドに割り当てます。さらに、ファイル I/O 用やネットワーク用にいくつかのバックグラウンド スレッドを使用する場合もあります。図 1 に、このようなスレッドとメインのデータ転送ポイントを示します。

図形 1.  Windows 向けゲームのスレッド設計

Bb204834.coding_for_multiple_cores_1(ja-jp,VS.85).gif

一般的な Xbox 360 ゲームでは CPU 負荷の大きいソフトウェア スレッドも使用できるため、図 2 に示すように、更新スレッド、レンダリング スレッド、および 3 つのワーカー スレッドに負荷を分割することがあります。

図形 2.   向けゲームのスレッド設計 Xbox 360

Bb204834.coding_for_multiple_cores_2(ja-jp,VS.85).gif

ファイル入出力とネットワークを除くこれらのタスクはすべて、独自のハードウェア スレッドで実行することが効果的であるほどの CPU 負荷を持つ可能性があります。またこれらのタスクは、1 フレームを通して通信を行わずに実行されるほど独立性が高い可能性があります。

ゲーム更新スレッドは、コントローラー入力、AI、物理計算を管理し、他の 4 スレッドに対する命令を準備します。この命令はゲーム更新スレッドが所有するバッファーに置かれるため、命令を生成する際には同期は必要ありません。

フレームの最後で、ゲーム更新スレッドは命令バッファーを他の 4 スレッドに渡した後、次のフレームの処理を開始して、新しい命令バッファーに入力します。

更新スレッドとレンダリング スレッドは交互に動作するため、それぞれの通信バッファーは単純に二重バッファリングされます。常に、更新スレッドが一方のバッファーに入力するときには、レンダリング スレッドがもう一方のバッファーを読み取っています。

その他のワーカー スレッドは、必ずしもフレーム レートに縛られているわけではありません。データの圧縮解除には、1 フレームかからない場合もあれば、何フレームもかかる場合もあります。布地や毛髪のシミュレーションでも、更新の頻度が少なくても問題ない場合があるため、フレーム レートに厳密に従って実行する必要がないことがあります。したがって、この 3 つのスレッドでは更新スレッドおよびレンダリング スレッドと通信するために別々のデータ構造体が必要になります。各スレッドに、作業要求を格納する入力キューが必要です。またレンダリング スレッドには、スレッドで生成された結果を格納するデータ キューが必要です。各フレームの最後で、更新スレッドは作業要求のブロックをワーカー スレッドのキューに追加します。更新スレッドでは同期オーバーヘッドを最小限に抑えるために、キューに追加するのは 1 フレームに 1 回のみです。各ワーカー スレッドでは、次のようなループを使用して、可能な限り速く作業キューから代入値を読み出します。

for(;;)
{
    while( WorkQueueNotEmpty() )
    {
        RemoveWorkItemFromWorkQueue();
        ProcessWorkItem();
        PutResultInDataQueue();
    }
    WaitForSingleObject( hWorkSemaphore ); 
}

データは更新スレッドからワーカー スレッドに渡された後、レンダリング スレッドに渡されるため、操作が画面に反映されるまで 3 フレーム以上の遅延が発生する可能性があります。しかし、遅延の影響を受けにくいタスクをワーカー スレッドに割り当てれば、これが問題になることはないでしょう。

その他には、複数のワーカー スレッドから同じ作業キューを読み出す設計方法があります。これにより自動負荷分散が実現され、すべてのワーカー スレッドがビジー状態を保つ可能性が高まります。

ゲーム更新スレッドではワーカー スレッドに作業を割り当てすぎないように注意する必要があります。そうしないと、作業キューが増大し続ける可能性があります。更新スレッドがこれを管理する方法は、ワーカー スレッドが処理しているタスクの種類によって変わります。

同時マルチスレッドとスレッド数

すべてのスレッドが同じであるわけではありません。2 つのハードウェア スレッドが別々のチップ上に存在することもあれば、同じチップ上に、あるいは同じコア上に存在することもあります。ゲーム プログラマが最も注意するべき構成は、1 つのコアに 2 つのハードウェア スレッドが存在する構成、つまり SMT (Simultaneous Multi-Threading) または HT テクノロジ (Hyper-Threading Technology) です。

SMT または HT テクノロジのスレッドは、CPU コアのリソースを共有します。スレッドは実行ユニットを共有するため、1 つのスレッドではなく 2 つのスレッドを実行した場合の速度向上の上限は通常 10% ~ 20% です。一方、独立した 2 つのハードウェア スレッドの場合の上限は 100% です。

さらに注意が必要なことは、SMT または HT テクノロジのスレッドが L1 命令とデータ キャッシュを共有することです。メモリー アクセス パターンに互換性がない場合、キャッシュを奪い合う結果となり、キャッシュ ミスが多数発生します。最悪の場合、第 2 スレッドの実行中に CPU コアの総パフォーマンスが低下する可能性があります。Xbox 360 では、これは非常に単純な問題です。3 つの CPU コアを持ち、各コアに 2 つのハードウェア スレッドがあるという Xbox 360 の構成は決まっているため、ソフトウェア スレッドを特定の CPU スレッドに割り当てた後、そのスレッド設計によってパフォーマンスの向上が得られるかどうかを確認できます。

Windows の場合、状況は複雑です。マシンによってスレッドの数やその構成が異なる上に、構成を調べる方法は複雑です。関数 GetLogicalProcessorInformation を使用すると、異なるハードウェア スレッド間の関係に関する情報を得られますが、この関数は Windows Vista で使用でき、Windows XP では使用できません。そのため現状では、使用できる "実際の" スレッドの数を判断するには、CPUID 命令および Intel や AMD が提供しているアルゴリズムを使用する必要があります。詳細については、参考文献を参照してください。

DirectX SDK の CoreDetection サンプルには、GetLogicalProcessorInformation 関数または CPUID 命令を使用して CPU コア テクノロジを返すサンプル コードが含まれています。CPUID 命令は、GetLogicalProcessorInformation が現在のプラットフォームでサポートされていない場合に使用されます。CoreDetection は次の場所に格納されています。

  • ソース :
    DirectX SDK root\Samples\C++\Misc\CoreDetection
  • 実行可能ファイル :
    DirectX SDK root\Samples\C++\Misc\Bin\CoreDetection.exe

最も安全な方法は、CPU 負荷の大きなスレッドを 1 つの CPU コアにつき 1 つまでにすることです。CPU 負荷の大きなスレッドを CPU コアの数よりも多くしても、ほとんど利点がない上に、スレッドが増えることによってオーバーヘッドや複雑性の増大を招きます。

スレッドの作成に関して

スレッドの作成は非常に単純な処理ですが、エラーの原因となる可能性が多くあります。次に、スレッドを適切に作成するコード例を示します。このコードではスレッド生成が終了するのを待ってから、クリーンアップします。

const int stackSize = 65536;
HANDLE hThread = (HANDLE)_beginthreadex( 0, stackSize,
            ThreadFunction, 0, 0, 0 );
// Do work on main thread here.
// Wait for child thread to complete
WaitForSingleObject( hThread, INFINITE );
CloseHandle( hThread );

...

unsigned __stdcall ThreadFunction( void* data )
{
#if _XBOX_VER >= 200
    // On Xbox 360 you must explicitly assign
    // software threads to hardware threads.
    XSetThreadProcessor( GetCurrentThread(), 2 );
#endif
    // Do child thread work here.
    return 0;
}

スレッドを作成する際、子スレッドにスタック サイズを指定できます。0 を指定すると、子スレッドが親スレッドのスタック サイズを継承します。Xbox 360 の場合、スレッドが開始されるときにスタックは完全にコミットされます。ほとんどの子スレッドは親スレッドほど多くのスタックを必要としないため、0 を指定すると大量のメモリーが無駄になる可能性があります。Xbox 360 では、スタック サイズが 64 KB の倍数となることも重要です。

CreateThread 関数を使用してスレッドを作成すると、Windows では C/C++ ランタイム (CRT) が適切に初期化されなくなります。その代わりに、CRT _beginthreadex 関数を使用することをお勧めします。

CreateThread または _beginthreadex から返された値が、スレッドのハンドルです。このスレッドを使用して子スレッドの完了を待つことができます。この方法は、ループを使ってスレッドのステータスを繰り返しチェックするよりもはるかに単純で効率的です。スレッドの完了を待つには、単純にスレッド ハンドルを使って WaitForSingleObject を呼び出します。

スレッドのリソースは、スレッドが完了し、なおかつスレッド ハンドルが閉じられるまで解放されません。そのため、スレッドが完了したときに CloseHandle を使用してスレッド ハンドルを閉じることが重要です。WaitForSingleObject を使ってスレッドの完了を待つ場合、待機が終わるまでハンドルを閉じないようにしてください。

Xbox 360 の場合、XSetThreadProcessor を使用してソフトウェア スレッドを特定のハードウェア スレッドに明示的に割り当てる必要があります。そうしなければ、すべての子スレッドが親スレッドと同じハードウェア スレッドに置かれます。Windows の場合、SetThreadAffinityMask を使用して、使用するスレッドが実行されるハードウェア スレッドをオペレーティング システムに強制的に指定することができます。一般的に、Windows ではこの方法は使用されません。システムで他にどんなプロセスが実行されているのかを判断できないためです。通常は、Windows スケジューラを利用してスレッドをアイドル状態のハードウェア スレッドに割り当てる方法をお勧めします。

スレッドの作成は負荷の大きな処理です。スレッドの作成と破棄は最小限にする必要があります。スレッドの作成と破棄を頻繁に行う必要がある場合は、その代わりに作業を待ち受けるスレッドのプールを使用してください。

スレッドの同期

複数のスレッドが協調して動作するには、スレッドを同期させ、メッセージを渡し、リソースへの排他的アクセスを要求できる必要があります。Windows および Xbox 360 には、さまざまな同期プリミティブが用意されています。同期プリミティブの詳細については、各プラットフォームのドキュメントを参照してください。

排他的アクセス

リソース、データ構造体、コード パスへの排他的アクセスを得ることは、良く求められることです。排他的アクセスを得る 1 つの方法は、ミューテックスです。その一般的な使用例を次に示します。

// Initialize
HANDLE mutex = CreateMutex( 0, FALSE, 0 );

// Use
void ManipulateSharedData()
{
    WaitForSingleObject( mutex, INFINITE );
    // Manipulate stuff...
    ReleaseMutex( mutex );
}

// Destroy
CloseHandle( mutex );
The kernel guarantees that, for a particular mutex, only one thread at a time can 
acquire it.
The main disadvantage to mutexes is that they are relatively expensive to acquire 
and release. A faster alternative is a critical section.
// Initialize
CRITICAL_SECTION cs;
InitializeCriticalSection( &cs );

// Use
void ManipulateSharedData()
{
    EnterCriticalSection( &cs );
    // Manipulate stuff...
    LeaveCriticalSection( &cs );
}

// Destroy
DeleteCriticalSection( &cs );

クリティカル セクションにはミューテックスと似たセマンティクスがありますが、これは 1 つのプロセス内での同期にのみ使用でき、プロセス間の同期には使用できません。主な利点は、ミューテックスの約 20 倍の速さで実行されることです。

イベント

2 つのスレッド (たとえば更新スレッドとレンダリング スレッド) が 2 つのレンダリング記述バッファーを交代で使用している場合、特定のバッファーの処理がいつ終わるのかを通知する手段が必要になります。そのためには、各バッファーにイベントを関連付けます (CreateEvent を使用して割り当てます)。スレッドでバッファーの処理が完了したときに、SetEvent を使用してこれを通知した後、もう 1 つのバッファーのイベントに対して WaitForSingleObject を呼び出します。この方法を利用することで、リソースのバッファリング量を簡単に 3 倍にすることができます。

セマフォ

セマフォは、実行できるスレッド数を制御するために使用されます。作業キューの実装によく使用されます。1 つのスレッドは 1 つのキューに作業を追加します。スレッドがキューに新しいアイテムを追加するときには、必ず ReleaseSemaphore を使用します。これにより、待機中スレッドのプールから 1 つのワーカー スレッドを解放することができます。ワーカー スレッドは単純に WaitForSingleObject を呼び出し、復帰する際には、キューに処理すべき作業項目があることを検出しています。さらに、共有作業キューに安全にアクセスできるようにするには、クリティカル セクションなどの同期方法を使用する必要があります。

SuspendThread を使用しない

スレッドの処理を停止する場合、適切な同期プリミティブではなく SuspendThread を使用してみようと思うかもしれません。しかし、これは適切な選択ではなく、デッドロックやその他の問題が発生する可能性が高い方法です。SuspendThread は、Visual Studio デバッガーとのやり取りも良好に行われません。SuspendThread は使用しないでください。代わりに、WaitForSingleObject を使用します。

WaitForSingleObject と WaitForMultipleObjects

関数 WaitForSingleObject は、最も一般的に使用される同期関数です。しかし、複数の条件が同時に満たされるまで、または一連の条件のうち 1 つが満たされるまでスレッドを待機させる場合もあります。そのような場合には、WaitForMultipleObjects を使用します。

インターロック関数とロックレス プログラミング

ロックを使用せずに単純なスレッド セーフ処理を実行する一連の関数があります。それが Interlocked 関数ファミリです。たとえば、InterlockedIncrement などが該当します。これらの関数と、フラグを注意深く設定する方法を合わせて、ロックレス プログラミングと呼びます。ロックレス プログラミングを適切に行うことは非常に困難です。さらに、Windows よりも Xbox 360 で行う場合の方がはるかに困難です。

ロックを使用しないプログラミングの詳細については、「Xbox 360 と Microsoft Windows でのロックレス プログラミングの考慮事項」を参照してください。

同期を最小限にする

一部の同期方法は、他の方法よりも高速に実行できます。しかし通常は、可能な限り高速な同期方法を選択してコードを最適化するよりも、同期の頻度を少なくする方が効果的です。その場合、同期の頻度が多い場合よりも高速に処理され、コードも単純化されるため、デバッグが容易になります。

メモリー割り当てなどの一部の処理では、正常に動作するために同期プリミティブを使用する必要があります。したがって、デフォルト共有ヒープから頻繁に割り当てると、同期が頻繁に行われ、パフォーマンスの無駄が発生します。頻繁な割り当てを避けるか、スレッド別のヒープ (HeapCreate を使用する場合は HEAP_NO_SERIALIZE を使用) を使用することで、この隠れた同期を回避できます。

D3DCREATE_MULTITHREADED を使用すると、Windows 上の D3D で多数の処理に対して同期が行われるため、これも隠れた同期の原因となります(Xbox 360 ではこのフラグは無視されます)。

スレッド ローカル ストレージとも呼ばれるスレッド別データは、同期を避けるための重要な手段となります。Visual C++ では、構文 __declspec(thread) を使用してグローバル変数をスレッド別に宣言することができます。

__declspec( thread ) int tls_i = 1;

これにより、プロセス内の各スレッドにそれぞれ tls_i のコピーが与えられます。tls_i は安全かつ効率的に参照でき、同期の必要がなくなります。

__declspec(thread) を使用した方法は、動的にロードされる DLL を使用する場合は機能しません。動的にロードされる DLL を使用する場合は、TLSAlloc 関数ファミリを使用してスレッド ローカル ストレージを実装する必要があります。

スレッドの破棄

スレッドを安全に破棄する唯一の方法は、メイン スレッドの関数から戻るか、スレッドで ExitThread または _endthreadex を呼び出すことで、スレッド自身によって終了させることです。スレッドが _beginthreadex によって作成されている場合、ExitThread を使用しても CRT リソースが適切に解放されないため、_endthreadex を使用するかメイン スレッドの関数から戻る必要があります。TerminateThread 関数は絶対に呼び出さないでください。スレッドが適切にクリーンアップされなくなります。スレッドは必ず自己解放するので、決して強制的に解放しないでください。

OpenMP

OpenMP は、プラグマを使用してコンパイラを並列化ループに導くことにより、プログラミングにマルチスレッド処理を追加するための言語拡張です。OpenMP は Windows および Xbox 360 の Visual C++ 2005 でサポートされています。手動のスレッド管理と組み合わせて使用できます。OpenMP はコードの一部をマルチスレッド化する便利な方法ですが、特にゲームの場合は理想的なソリューションとは言えません。OpenMP は、アートなどのリソースの処理などの長期間の制作作業に適している場合があります。詳細については、Visual C++ のドキュメントを参照するか、OpenMP の Web サイトにアクセスしてください。

プロファイリング

マルチスレッド化されたプロファイリングは重要です。マルチスレッドでは、スレッドが互いを待ち受けることによって長時間のストールが容易に発生します。このようなストールは発見や診断が難しい場合があります。ストールの発見を容易にするために、同期呼び出しにインストルメントを追加することを検討してください。また、サンプリング プロファイラーはタイミング情報をほとんど変更せずに記録できるため、このような問題の識別に役立ちます。

タイミング

rdtsc 命令は、Windows で正確なタイミング情報を取得する方法の 1 つです。残念ながら、rdtsc には複数の問題があり、製品版タイトルに使用するには貧弱な機能です。rdtsc カウンターは CPU 間で同期されるわけではないため、使用しているスレッドをハードウェア スレッド間で移動すると、正または負の差が大きく発生することがあります。省電力設定によっては、ゲームを実行する際に rdtsc カウンターのインクリメント頻度も変化することがあります。このような問題を回避するには、QueryPerformanceCounterQueryPerformanceFrequency を使用することをお勧めします。これにより、製品版ゲームで高精度なタイミングを利用できます。タイミングの詳細については、「ゲームのタイミングとマルチコア プロセッサ」を参照してください。

デバッグ

Visual Studio では、Windows および Xbox 360 向けのマルチスレッド デバッグが完全にサポートされています。Visual Studio の [スレッド](threads) ウィンドウでは、スレッドの表示を切り替えて、さまざまな呼び出し履歴やローカル変数を確認することができます。また [スレッド](threads) ウィンドウを使用して、特定のスレッドの凍結と解凍を行えます。

Xbox 360 の場合、[ウォッチ](watch) ウィンドウで @hwthread メタ変数を使用して、現在選択しているソフトウェア スレッドが実行されているハードウェア スレッドを表示できます。

スレッドにわかりやすい名前を付けておくと、[スレッド](threads) ウィンドウは使いやすくなります。Visual Studio やその他の Microsoft デバッガーでは、スレッドに名前を付けることができます。次の SetThreadName 関数を実装して、各スレッドの起動時に呼び出します。

typedef struct tagTHREADNAME_INFO
{
    DWORD dwType;     // must be 0x1000
    LPCSTR szName;    // pointer to name (in user address space)
    DWORD dwThreadID; // thread ID (-1 = caller thread)
    DWORD dwFlags;    // reserved for future use, must be zero
} THREADNAME_INFO;

void SetThreadName( DWORD dwThreadID, LPCSTR szThreadName )
{
    THREADNAME_INFO info;
    info.dwType = 0x1000;
    info.szName = szThreadName;
    info.dwThreadID = dwThreadID;
    info.dwFlags = 0;

    __try
    {
        RaiseException( 0x406D1388, 0,
                    sizeof(info) / sizeof(DWORD),
            (DWORD*)&info );
    }
    __except( EXCEPTION_CONTINUE_EXECUTION ) {
    }
}

// Example usage:
SetThreadName(-1, "Main thread");

カーネル デバッガー (KD) および WinDBG でもマルチスレッド デバッグがサポートされています。

検証

マルチスレッド プログラミングは扱いが難しく、一部のマルチスレッド バグはごくまれにしか現れないため、このようなバグを発見して修正することは困難です。バグを洗い出す最も良い方法は、さまざまな構成のマシンで検証することです。特に、4 個以上のプロセッサを搭載したマシンが適しています。1 スレッドのマシンでは完璧に動作するマルチスレッド コードも、4 プロセッサのコンピューターで実行するとすぐに失敗することがあります。AMD CPU と Intel CPU ではパフォーマンスとタイミング特性が大きく異なる場合があるため、両方のベンダーのマルチプロセッサ マシンで検証するようにしてください。

まとめ

注意深い設計によりスレッド間の通信を最小限にすることで、コードを複雑にしすぎることなく、マルチスレッド プログラミングによる大幅なパフォーマンス向上を達成できます。これによって、ゲーム プログラムでは次世代プロセッサの性能を活用して、思わず引き込まれてしまうようなゲーム体験を実現できます。

参考文献