ゲームのタイミングとマルチコア プロセッサ
今日のコンピューターでは電源管理テクノロジが一般的になり、高分解能の CPU タイミング (RDTSC 命令) を取得するために一般的に使用される方法が想定どおりに動作しなくなる可能性があります。 この記事では、Windows API QueryPerformanceCounter と QueryPerformanceFrequency を使用して高分解能の CPU タイミングを取得するための、より正確で信頼性の高いソリューションを提案します。
背景
x86 P5 命令セットの導入以来、多くのゲーム開発者は、RDTSC 命令である読み取りタイム スタンプ カウンターを使用して高解像度のタイミングを実行しています。 Windows マルチメディア タイマーは、サウンドとビデオの処理に十分な精度を備えていますが、フレーム時間が 12 ミリ秒以下の場合、デルタタイム情報を提供するのに十分な解像度がありません。 多くのゲームでは、起動時にマルチメディア タイマーを使用して CPU の周波数を確立し、その頻度値を使用して RDTSC からの結果をスケーリングして正確な時間を取得します。 RDTSC の制限により、Windows API は QueryPerformanceCounter と QueryPerformanceFrequency のルーチンを使用してこの機能にアクセスするためのより正しい方法を公開します。
タイミングに RDTSC を使用すると、次の基本的な問題が発生します。
- 不連続の値。 RDTSC を直接使用すると、スレッドは常に同じプロセッサで実行されていることを前提としています。 マルチプロセッサシステムとデュアルコアシステムでは、コア間のサイクルカウンターの同期は保証されません。 これは、異なるタイミングでさまざまなコアをアイドル状態にして復元する最新の電源管理テクノロジと組み合わせると悪化し、通常はコアが同期されなくなります。 アプリケーションの場合、通常、スレッドがプロセッサ間をジャンプし、大きなデルタ、負のデルタ、または停止したタイミングをもたらすタイミング値を取得すると、グリッチまたは潜在的なクラッシュが発生します。
- 専用ハードウェアの可用性。 RDTSC は、アプリケーションがプロセッサのサイクル カウンターに要求するタイミング情報をロックします。 長年にわたり、これは高精度のタイミング情報を得るための最良の方法でしたが、新しいマザーボードにはRDTSCの欠点なしに高解像度のタイミング情報を提供する専用のタイミングデバイスが含まれています。
- CPU の周波数の変動性。 多くの場合、プログラムの有効期間中に CPU の周波数が固定されることを前提とします。 ただし、最新の電源管理テクノロジでは、これは正しくない前提です。 最初はラップトップコンピュータやその他のモバイルデバイスに限定されていましたが、CPUの周波数を変更する技術は、多くのハイエンドデスクトップPCで使用されています。一貫した頻度を維持するために関数を無効にすることは、一般にユーザーには受け入れできません。
Recommendations
ゲームには正確なタイミング情報が必要ですが、RDTSC の使用に関連する問題を回避する方法でタイミング コードを実装する必要もあります。 高解像度のタイミングを実装する場合は、次の手順を実行します。
RDTSC の代わりに QueryPerformanceCounter と QueryPerformanceFrequency を使用します。 これらの API は RDTSC を利用できますが、代わりに、マザーボード上のタイミング デバイスや、高品質の高解像度タイミング情報を提供する他のシステム サービスを利用する場合があります。 RDTSC は QueryPerformanceCounter よりもはるかに高速ですが、後者は API 呼び出しであるため、顕著な影響を与えることなくフレームあたり数百回呼び出すことができる API です。 (ただし、開発者は、パフォーマンスの低下を回避するために、ゲームで QueryPerformanceCounter をできるだけ呼び出さないようにする必要があります)。
デルタを計算するときは、タイミング値のバグによってクラッシュや不安定な時間関連の計算が発生しないように、値をクランプする必要があります。 クランプ範囲は 0 (負の差分値を防ぐために) から、予想される最小のフレームレートに基づいて妥当な値に設定する必要があります。 クランプは、アプリケーションのデバッグで役立つ可能性がありますが、パフォーマンス分析を行ったり、最適化されていないモードでゲームを実行したりする場合は、必ず注意してください。
1 つのスレッドですべてのタイミングを計算します。 特定のプロセッサに関連付けられた各スレッドなど、複数のスレッドでのタイミングの計算により、マルチコア システムのパフォーマンスが大幅に低下します。
Windows API SetThreadAffinityMask を使用して、単一のスレッドを 1 つのプロセッサに残すように設定します。 通常、これはメインゲーム スレッドです。 QueryPerformanceCounter と QueryPerformanceFrequency は通常、複数のプロセッサに合わせて調整されますが、BIOS またはドライバーのバグにより、スレッドが 1 つのプロセッサから別のプロセッサに移動すると、これらのルーチンが異なる値を返す可能性があります。 そのため、スレッドを 1 つのプロセッサに保持することをお勧めします。
他のすべてのスレッドは、独自のタイマー データを収集せずに動作する必要があります。 同期のボトルネックになるため、ワーカー スレッドを使用してタイミングを計算することはお勧めしません。 代わりに、ワーカー スレッドはメイン スレッドからタイムスタンプを読み取る必要があります。ワーカー スレッドはタイムスタンプのみを読み取るため、重要なセクションを使用する必要はありません。
QueryPerformanceFrequency を 1 回だけ呼び出します。これは、システムの実行中に頻度が変更されないためです。
アプリケーションの互換性
多くの開発者は長年にわたって RDTSC の動作を想定しているため、タイミングの実装により、複数のプロセッサまたはコアを持つシステムで実行すると、既存のアプリケーションで問題が発生する可能性が非常に高くなります。 これらの問題は、通常、グリッチまたはスローモーションの動きとして現れます。 電源管理を認識していないアプリケーションには簡単な解決策はありませんが、マルチプロセッサ システムの 1 つのプロセッサでアプリケーションを常に実行するように強制するための既存の shim があります。
この shim を作成するには、Windows アプリケーション互換性から Microsoft Application Compatibility Toolkit をダウンロードします。
ツールキットの一部である互換性管理者を使用して、アプリケーションと関連する修正プログラムのデータベースを作成します。 このデータベースの新しい互換モードを作成し、互換性修正 プログラム SingleProcAffinity を選択して、アプリケーションのすべてのスレッドを 1 つのプロセッサ/コアで強制的に実行します。 コマンドライン ツール Fixpack.exe (ツールキットの一部) を使用すると、このデータベースをインストール、テスト、および配布用のインストール可能パッケージに変換できます。
互換性管理者の使用手順については、ツールキットのドキュメントを参照してください。 Fixpack.exeの構文と使用例については、コマンド ライン ヘルプを参照してください。
顧客指向の情報については、Microsoft ヘルプとサポートの次のサポート情報記事を参照してください。
- QueryPerformanceCounter 関数を使用するプログラムは、Windows Server 2003 および Windows XP でパフォーマンスが低下する可能性があります (記事 895980)