Skip to main content
CPU メーカーは、パフォーマンス、消費電力、ダイ面積のバランスを常に取っています。理由は、顧客ごとに異なる利用パターンと優先順位があるためです。デスクトップ マシンを使う人はパフォーマンスに関心があり、ノート PC ユーザーはバッテリー寿命により関心があります。ダイ面積を小さくすれば製造コストが下がります。こうした競合する要件に対処するために、CPU メーカーは ヘテロジニアス アーキテクチャ を設計し、コスト効率を維持しつつ現在のワークロード要求にシステムが動的に適応できるようにしています。 CPU メーカーは、異なる効率レベルの CPU コアを使ってヘテロジニアス トポロジを実装しました。このアプローチは、既存の CPU アーキテクチャの一部のコアを、より電力効率の高いコアに置き換えたものです。ユーザーが CPU のフル パフォーマンスを必要としない場合、OS はより高性能なコアをオフにして電力を大幅に節約し、バッテリー寿命を延ばします。より高い性能が必要なときは、OS はより高性能なコアを動的にオンにして増加したワークロードに対処できます。

ハードウェア

各独立系ハードウェア ベンダー (IHV) は、ヘテロジニアス CPU トポロジを異なる形で定義しています。Intel と Qualcomm は PerformanceEfficiency という用語を使用し、AMD は PerformanceCompact を使用します。各ベンダーは、コア トポロジを設計する際にパフォーマンスと効率の間でトレードオフを行います。 以下の例は、関わってくる設計選択の一部を示しています。
  • 異なるキャッシュ サイズ
    • 異なるコアで異なるキャッシュ サイズを持つ場合があります。
    • たとえば、高性能コアはより大きな L1 と L2 キャッシュを持つ場合があります。
  • キャッシュの共有
    • コア間のキャッシュ共有には多様なレベルがあり得ます。
    • たとえば、高性能コアは各コアがプライベート L2 キャッシュを持つ一方、高効率コアは単一の L2 キャッシュを共有する場合があります。
  • 対称マルチ スレッディング (SMT) のサポート
    • 一部のコアは SMT をサポートし、他のコアはサポートしない場合があります。
    • Intel は SMT を Hyper-Threading と呼びます。
  • 内部コア レイアウト
    • 各種コアの内部リソースは異なる場合があります。
    • たとえば、高性能コアはより多くの浮動小数点パイプ、ALU パイプ、または広いデータ レーンを含む場合があります。
  • 最大周波数
    • コア間で最大周波数が異なる場合があります。
    • たとえば、高性能コアは高効率コアよりも高い周波数を達成できる場合があります。
これらの変更は、CPU 内のコア間でパフォーマンスと消費電力のバランスを取るように設計されています。より高い周波数を持つコアは、より高いパフォーマンスを持ちますが、消費電力が増加します。同じキャッシュを共有する複数のコアはダイ サイズを小さくし、消費電力を削減しますが、パフォーマンスは低下します。目標は、パフォーマンスと消費電力についてより動的な選択を可能にすることです。ユーザーがフル パフォーマンスを必要としない場合、OS はより効率的なコアに切り替えて消費電力を削減します。 Windows には、CPU 上のすべてのコアが同じ Instruction Set Architecture (ISA) をサポートするという要件があることに注意することが重要です。この要件は、CPU が特定の命令セットをサポートするなら、その CPU のすべてのコアがそのセットをサポートすることを意味します。たとえば、CPU が AVX2 をサポートすると言えば、すべてのコアが AVX2 をサポートします。 高性能コアと高効率コアの違いに関する具体的な詳細については、各 IHV の公式ドキュメントを参照してください。

パフォーマンスの数値

異なる効率レベルのコアは異なるパフォーマンスを持つと予想されます。1 つはパフォーマンス向けに、もう 1 つは節電向けに設計されています。問題は、コア間でどれほど異なるかということです。 示すデータは IHV 横断の集合体です。各 IHV は自社トポロジの選択に基づき、テストごとに異なるスケーリングを持ちます。厳密な数値は異なりますが、パターンはすべての IHV にわたって類似しています。また、CPU 世代ごとに異なるトレードオフを行うため、相対的な数値が調整されます。既存のすべての IHV と世代にわたる数値の作成は、この記事の範囲を超えています。

合成テスト

ベクトル演算は、ゲーム タイトルがフレーム内で行う作業の大部分を占めます。このワークロードは、物理演算、AI、レンダリングまで多岐にわたる可能性があります。ゲームはこの作業を、単一の 128 ビット SSE レジスタに収まる 3 つまたは 4 つの float を持つベクトル型を使って実行します。ドット積、行列乗算、平方根計算、ベクトル変換など幅広い演算が対象となります。これらの演算のパフォーマンスの違いは、全体のフレーム時間に大きな影響を与える可能性があります。 以下のグラフは、7 つの一般的な数学演算を実行するときの相対的コストを示しています。測定には、単一の 128 ビット SSE レジスタに格納された 4 つの 32 ビット浮動小数点値のベクトルを使用します。行列は 4 つのベクトルで構成され、合計 16 個の 32 ビット浮動小数点値が 4 つの 128 ビット SSE レジスタに配置されます。これらの演算は、フレーム内で一般的に使用されるため選ばれました。
  • ドット積: ベクトルのドット積を計算します。
  • ベクトル変換: ベクトルを行列で乗算して変換します。
  • ベクトル加算: 2 つのベクトルを加算します。
  • クロス積: 2 つのベクトルのクロス積を計算します。
  • ベクトル sine: ベクトルの各要素の sine の近似を計算します。
  • ベクトル FMA (fused multiply-add): 2 つのベクトルを乗算し、3 つ目を加算します。
  • 逆平方根: ベクトルの長さの逆平方根を計算します。
図 1: 演算実行にかかった時間。低いほど良い。 Heterogeneous Synthetic Benchmark Numbers このグラフの y 軸は演算実行にかかった時間です。各バーは、すべての IHV にわたって測定された演算時間の範囲を表します。高性能コアまたは高効率コアでテストを実行したときの最小および最大時間です。 期待どおり、コア タイプ間のパフォーマンスは異なります。ただし、これらの数値からの重要な洞察は、すべての演算と IHV でパフォーマンスがスケールしないことです。IHV 間の結果の変動性は概ね類似する傾向にありましたが、より変動性の高いものもありました。より変動性の高い演算は、Vector Sin と Inverse Square Root の演算で最も顕著に現れました。この違いは、高性能コアと高効率コア間のコア アーキテクチャの選択に起因します。この差の正確な原因は複数のソースから来る可能性があります。たとえば、高性能コアはより多くの Floating Point パイプを持ち、より多くの演算が並列で行える場合があります。正確な理由は各 IHV と CPU 世代に固有です。特定の演算でパフォーマンスの差が存在しない構成すらあるかもしれません。結局のところ、タイトルは IHV 間のこの変動性を考慮する必要があります。

実世界

前述の数値は、単独の特定のベンチマークを示しており、話の一部にすぎません。以下の数値は、2 つのメイン スレッド(Simulation と Render)とジョブ スレッドのセットを使用する AAA ゲーム エンジンのベンチマークからのものです。ベンチマークでは 3 つのテスト構成を使用します。
  • 2 つのメイン スレッドは高性能コアでのみ実行されます。
  • 2 つのメイン スレッドは高効率コアでのみ実行されます。
  • 2 つのメイン スレッドはシステム内のすべてのコアで実行されます。
各テスト構成において、ジョブ スレッドはシステム内のすべてのコアにわたって自由に動くことができます。レンダリング作業は可能な限り低い解像度と品質を使用します。この設定により、タイトルはテスト実行全体で CPU バウンドであることが保証されます。この設定はまた、Simulation スレッドをフレーム レートの原動力にします。 このグラフは、フレーム中に各スレッドで費やされた時間を示しています。平均フレーム時間と 1% high の両方を示しています。1% high は、最長 1% のフレームの閾値です。すべてのフレームの 1% のみがこの値を超えます。この数値は、スタッター フレームの数の良い感覚を提供します。平均から遠ざかるほど、タイトルのスタッターは悪化します。すべての場合において、値が低いほど長いフレームが少なく、目に見えるスタッターが少ないことを示します。 前述のとおり、示すデータは IHV 間の集合体です。各 IHV は、そのトポロジに対して行った選択に基づいて、テストごとに異なるスケーリングを持ちます。厳密な数値は異なりますが、パターンは IHV 間で類似しています。CPU 世代ごとに異なるトレードオフを行うため、相対的な数値が調整されます。既存のすべての CPU IHV と世代にわたる数値の作成は、このペーパーの範囲外です。 図 2: 平均所要時間と 1% high。低いほど良い。 Heterogeneous Real World Benchmark Numbers このグラフの y 軸は 1 フレームの所要時間です。各バーは、すべての IHV にわたって測定された演算時間の範囲を表します。テストされた各スレッド トポロジの最小および最大時間です。 OS のスレッド スケジューラーは、スレッドが実行する作業量を判断するためにヒューリスティックを使用します。これらのヒューリスティックは、OS の選択と IHV によるドライバー調整の組み合わせです。スケジューラーはこの情報を使って、特定のスレッドがどのコアで実行されるべきかを選択します。あるスレッドが大量の作業を行っていると判断すると、スケジューラーはそのスレッドをより高性能なコアに移動させます。 スケジューラーのヒューリスティックのため、クリティカル スレッドが自由に動けるようにするとタイトルがより速く実行される、といった直感に反する挙動が発生することがあります。この挙動は常に起こるわけではなく、ワークロード、コア トポロジ、正確なスケジューラーのヒューリスティックに依存します。 ここでテストされたケースでは、Simulation スレッドは Render スレッドよりもフレームあたりに実行される時間が長いです。スケジューラーは、このスレッドをより高性能なコアに配置することを好みます。また、Render スレッドは作業待ちで停止している時間の方が長いため、より性能の低いコアに配置することを決定します。正味の効果として、この決定によりジョブ スレッドの 1 つに対してより高性能なコアが空きます。Render スレッドがより多くの作業を実行していたら、この状況は発生しないかもしれません。 しかし、このグラフから最も興味深いのは 1% high の数値です。クリティカル スレッドが自由に動く場合、平均が同じであっても、1% high はクリティカル スレッドを高性能コアにロックした場合よりも悪化しています。この状態は、クリティカル スレッドが自由に動く場合にフレーム レートがよりスタッターすることを意味します。最も一般的な理由は、スケジューラーのヒューリスティックがスライディング ウィンドウ上で動作することです。フレームに必要な作業のスパイクに反応するのに時間がかかります。その時間中、クリティカル スレッドは性能の低いコアで実行される可能性があります。
各メーカーは、自社ハードウェアに合わせてこれらのスケジューラー ヒューリスティックを調整できます。たとえば、AMD には Workload Profile Scheduling があり、Intel には Thread Director テクノロジがあります。各 IHV は SoC の特定のトポロジに合わせてヒューリスティックを調整する傾向があります。

推奨事項

スレッド アフィニティ

単一の効率クラスしか持たないデスクトップ マシンで実行する場合、スレッド アフィニティに関する助言は、ハード スレッド アフィニティを設定せず、代わりにスレッドがすべてのコアにわたって自由に動くようにすることです。この不確実性の主な理由は、ユーザーのマシンで他のプロセスがあらかじめ実行されている可能性があるためです。ハード アフィニティを持つスレッドが、システム内の別の高優先度スレッドとコアを共有することになる可能性があります。正味の効果として、タイトル スレッドは実行時間に飢えます。この競合がフレーム クリティカルなスレッドで発生すると、タイトルは一貫しないフレーム時間を経験する可能性があります。OS がタイトル スレッドを移動させて実行し続ける場所があることを確認したいのです。 しかし、この助言はヘテロジニアス CPU で実行する場合には変わる必要があります。主な理由は、OS はタイトルのどのスレッドやジョブがフレーム クリティカルかを必ずしも知らないためです。フレーム クリティカル スレッドと思われるものを判定するヒューリスティックはありますが、そのヒューリスティックは依然として誤った選択を行う可能性があります。それらのスレッドやジョブの 1 つを性能の低いコアに移動させ、その結果、タイトルにおけるパフォーマンス問題の可能性を高める場合があります。 推奨事項は、タイトル スレッドが最小で 3〜4 コアで実行できるようにすることを目指し、さらに多い方が望ましいということです。たとえば、1 つの高性能コアと 3 つの低性能コアを持つローエンド システムでは、スレッドをすべてのコアにわたって自由に動かすべきです。3 つ以上の高性能コアを持つマシンでは、フレーム クリティカルなスレッドをそれら高性能コアのみに制限できます。 多くのコアを持ち SMT もサポートするハイエンド マシンでは、一部のタイトルはスレッドを物理コアにロックしようとするかもしれません。この手法は、複数の高優先度スレッドが物理コアの同じリソースを共有する物理コア レベルでの競合を回避します。ただし、一般的には、このケースを検討することは推奨しません。OS はすでにこの挙動をシステム全体に適用しています。まず物理コアにわたってスレッドをスケジュールし、必要な場合にのみ論理コアをさらに使用します。 タイトルが検討すべき唯一のケースは、タイトルのクリティカル スレッドをサポートするのに十分な高性能コアがシステム内にある場合です。この場合、フレーム クリティカル スレッドを高性能コアにロックすることが望ましいかもしれません。ただし、それらのスレッドはすべての高性能コアにわたって自由に動けるようにする必要があります。すべてのスレッドをすべてのコアにわたって自由に動かすとパフォーマンスが向上する場合がありますが、この挙動は CPU メーカー間で一貫していません。 タイトルがすべてのスレッドにわたってワークロードを完全にバランスできるワーク スティーリング システムを実装している場合、任意のジョブ スレッドはすべてのコアにわたって自由に動くようにすべきです。高優先度でも低優先度でも構いません。ワーク スティーリング アルゴリズムが自動的に負荷をバランスし、最も多くの作業を最短時間で完了できるようにします。より高性能なコア上のスレッドは、フレーム中により多くのジョブを実行します。 複数のフレーム クリティカル スレッドがある場合、それらのアフィニティを、2 つのフレーム クリティカル スレッドが同じコアで実行されないように設定すると便利な場合があります。たとえば、2 つのフレーム クリティカル スレッドがある場合、それらのアフィニティを互いの排他的論理和 (XOR) に設定すると、OS がスレッドを競合させる可能性を排除できます。ただし、繰り返しますが、このオプションは、各フレーム クリティカル スレッドが最小で 3〜4 コアにわたって自由に動くのを許容しつつ、このケースをサポートするのに十分なコアがある場合にのみ検討してください。

スレッド トポロジ

ヘテロジニアス環境でスレッドに作業を割り当てる方法を考える際、主なデータは各種タスクの長さと相互作用に関するものです。各タスクの平均長さは何か、タスク間の依存関係は何か、各タスクの優先順位は何か、などです。 ジョブ スレッドは、クリティカル スレッドやジョブの時間スパイクをどれだけ吸収できるでしょうか? それらのスレッドやジョブの 1 つが 35% 遅くなった場合、フレーム時間にどれほど影響するでしょうか? たとえば、あるタスクが長引いた場合、この遅延がフレームの残りにどれほど影響するか、システムはそのタスクが完了するまでストールするでしょうか? クリティカル スレッドやジョブに余分なオーバーヘッドを加えることで、ヘテロジニアス システムがフレーム時間にどれほど影響するかを判定することは有用です。テストとして、ランダムにジョブや作業ブロックが余分に 25%〜35% の時間を要するようにしてください。全体のフレーム時間の変化の度合いは、ジョブ システムが実行スパイクをどれだけ吸収できるかを示します。理想的な状況は、フレーム時間がほとんど変化しないことです。それは、システムが任意のフレーム時間スパイクを吸収できることを意味します。スパイクは、性能の低いコアで実行されることによるものか、CPU 時間を使うシステム内の別のランダム プロセスから来るものです。

スレッド通信

ロック プリミティブを使用したスレッド通信については、多少の考察が必要です。コンテキスト スイッチのオーバーヘッドは、新しいスレッドが実行を開始する前の受信側コアの C-State と密接に結びついています。新しいスレッドが実行を開始する前に、コアは実行状態である C0 になっている必要があります。最も安価なのは、コアがすでに実行中で、すでに C0 にある場合です。ただし、コアがアイドル状態なら、より低い C-State にあります。C-State のレベルは、より深いスリープ レベルに対応します。より深いスリープは、コアの消費電力を減らします。ただし、より深いスリープから起きて実行を再開するのはより高コストです。最も一般的な低電力 C-state は C1 で、2〜7 μs のオーバーヘッドを追加できます。次の C-state である C2 は、約 40〜100 μs のオーバーヘッドを追加できます。正確な数値は IHV と SoC 固有です。アイドル時のコアがどの C-State にあるかを制御する変数は多くあり、応答性と消費電力のバランスです。より低い C-State はより高い C-State よりも消費電力が少ないです。コアはまずアイドル時に C1 に入り、しばらくコードを実行していなければ C2 に切り替わることが一般的です。 もう 1 つの懸念は、ジョブを送信するスレッドとジョブを実行するスレッドが異なるパフォーマンス プロファイルを持つコアにある場合に何が起こるかです。技術的には、ジョブ スレッドが新しいジョブが作成されるよりも速くジョブを完了できることが可能です。ジョブを実行するスレッドがジョブを送信するスレッドより高速なコアにある場合、この状況が発生する可能性が高くなります。 ジョブの実行時間が作成時間よりも短い場合、タイトルはジョブ スレッドが新しいジョブを待つために絶えずサスペンドされる状況に陥る可能性があります。正味の効果は、ジョブ スレッドが絶えずサスペンドされるため、ジョブの実行が高コストになることです。コンテキスト スイッチのオーバーヘッド コストが各ジョブの実行時間に加算されます。このオーバーヘッドは、逆説的により高性能なコアで実行するときにフレーム レートが低下する原因となり得ます。たとえば、以前は 250 μs かかっていた作業が突然 275 μs かかり始めることがあり、この後退はパフォーマンスの 10% 低下です。 このケースのため、ジョブは作成時に個別に送信するのではなく、バッチで送信することが推奨されます。たとえば、システムがジョブを作成、送信、作成、送信、というパターンに従う場合、代わりにビルドのバッチを最初に実行してから一度にすべて送信するようにパターンを調整すべきです。 タイトルが WaitForSingleObject を必要とするオブジェクトを使用している場合、小さなスピン ループ内に配置すべきです。タイムアウトを 0 として関数を呼び出し、スピンが終わったらタイトルの通常のタイムアウト値を使用してください。ただし、この問題の最良の解決策は、Slim Reader/Writer (SRW) LocksCritical SectionsWaitOnAddress のような近代的な同期 API を使用することです。これらの同期 API はいずれも内部に何らかのスピンを含み、スレッドが絶えずサスペンドされるのを防ぎます。このスピンは、短時間だけ保持されるロックにとって特に有用です。 長いスピン ロックを避けることが重要です。たとえば、スレッドが実行する作業を絶えず待ち続けるパターンです。コアで他の作業を実行する必要がある場合、スケジューラーは最終的にスピンしているスレッドをサスペンドしてその作業を実行します。このプリエンプションが発生すると、サスペンドされたスレッドはかなりの時間サスペンドされたままである可能性が高くなります。この挙動は、他のスレッドの飢餓を防ぐために行われます。タイトルはサスペンドが発生するタイミングをほとんど制御できないため、フレーム レートのスタッターにつながる可能性があります。

ジョブ システム

ベスト パフォーマンスのため、エンジンを専用の長時間実行フレーム クリティカル スレッドに依存させるのではなく、ジョブを中心に構造化してください。たとえば、Simulation/Render スレッド モデルは 2 つの永続的なフレーム クリティカル スレッドを作成します。ただし、長時間のジョブは、任意のジョブ スレッドがフレーム クリティカル スレッドとして振る舞う原因になり得ます。ジョブが 10 ms かかると、そのスレッドはその期間、元の役割に関係なくフレーム クリティカルになります。

ワーク スティーリング

最重要の推奨事項は、ジョブ システム全体でワーク スティーリングを使用し、フレーム クリティカル スレッドもワーク スティーリング アルゴリズムに参加させることです。たとえば、レンダー スレッドがレンダー関連のジョブの完了を待っている場合、それらのジョブの一部を実行すべきです。 この挙動は、多くのゲーム エンジンですでに一般的な実践です。ただし、ヘテロジニアス コアと OS プリエンプションによるフレーム スパイクをゲーム エンジンが吸収できるためには、このような挙動が重要であるため、ここで言及する価値があります。 ジョブが実行に長時間かかると、フレーム時間スパイクのリスクが増加します。この挙動は、性能の低いコアでジョブが実行されるか、PC 内の他の作業によって発生することがあります。エンジンは、ジョブがフレームの約 2% を占めるように目指すことが推奨されます。この予算は、たとえば 60 fps で実行するゲームでは約 333 μs 以下ということです。必要に応じて、複数の小さなジョブをより大きなジョブにバッチ処理して、2% の数値を目指すべきです。このアプローチは、ジョブ サイズ、スレッド通信、スパイクの吸収能力の良いトレードオフを提供します。 これらの例は、長いフレーム クリティカル ジョブがシステム パフォーマンスに与える影響を示しています。これらの例では、各ジョブは同じ数の演算で構成されています。この例では、1 つのジョブが遅いコアで 375 μs、速いコアで 250 μs かかります。長い柱ジョブは他のジョブの 6 倍高コストです。 最悪のケースは、長い柱ジョブが遅いコアの 1 つに割り当てられた場合です。これはフレーム時間に劇的な影響を与えることがあります。このシナリオでは、作業を実行する総時間は 2,250 μs で、375 μs の 6 倍です。 図 3: 遅いコアで実行される長い柱の例。 Heterogeneous Work Stealing long pole example on a slower core 長い柱ジョブが速いコアに割り当てられれば状況は改善します。この場合、総時間は 1,500 μs です。まだ素晴らしいとは言えませんが、より良くなります。 図 4: 速いコアで実行される長い柱の例。 Heterogeneous Work Stealing long pole example on a faster core 最良の結果は、ジョブ サイズを小さく均等に分散することで長い柱ジョブを避けることです。この例では、総実行時間は 1,125 μs に減少し、これは前の例の半分の時間で、同じ量の作業が完了しています。 図 5: 適切なジョブ長を使うことで長い柱ジョブを避ける。 Heterogeneous Work Stealing with proper job length さらに重要なのは、ジョブの実行中に OS が割り込もうとした場合、システムが自動的にワークロードをバランスすることです。この場合、OS は速いコアで 375 μs のタスクでタイトルに割り込みました。この再バランスは全体のフレーム時間に影響せず、1,125 μs のままです。ジョブの 1 つは遅いコアの空いた時間に移行しました。 図 6: OS の割り込みでもワークロード再バランスにより安定したフレーム時間を維持する。 Heterogeneous Work Stealing with proper job length with interruption これらの例は、長い柱ジョブがすぐに開始できる場合のみをカバーしています。ジョブ キュー内で後まで開始できない場合、状況はさらに悪化します。それはすべて Amdahl’s Law に戻ります。ここでは、あらゆるシステムを実行する時間は、そのシステムの最も長く実行される部分に結び付けられます。エンジンは、そういった長い柱スレッドの実行時間を増加させるスパイクを吸収することは不可能です。これらのスパイクは、遅いコアで実行することや、システム内の他の高優先度プロセスからの割り込みから来ることがあります。

まとめ

これらの推奨事項を実装することで、ゲームが確実に動作し、異なる CPU アーキテクチャや他の Windows プロセスからの干渉に対して堅牢であり続けるようにできます。

付録

コアの効率クラスを判定する

GetLogicalProcessorInformationEx 関数を使ってシステム内の各プロセッサの効率クラスを判定してください。関数から返されるデータを繰り返し処理し、効率クラスとコア マスクを含む RelationProcessorCore ブロックを探します。

消費電力に基づいてスレッドをピン留めする

前述のとおり、マシン上の他のプロセスからの干渉の可能性のため、デスクトップ環境ではアフィニティを使用したコアのピン留めは推奨されません。ただし、Windows はタイトルが OS に作業をどこで実行すべきかを推奨するヒントを与える方法を提供しています。この案内の 1 つの方法は、電源管理 API を通じてです。

CPU Sets を使って効率クラスを照会する

CPU Sets を使うことは、システム内の各コアの効率クラスを照会するもう 1 つの方法です。

CPU Sets を使ってスレッドを効率クラスに割り当てる

CPU Sets は、スレッドに対して明示的にアフィニティ マスクを設定する代わりに使用することもできます。明示的なアフィニティ マスクの代わりに CPU Sets を使うことが推奨されます。
最終更新日 2026年8月13日