CPUのブラックボックスをブラックでなくす - TMA_トップダウンのCPUアーキテクチャ性能ボトルネック解析手法 What & Why---HOW---フロントエンド---推測---引退 (未完)

 何となぜ

TMA メソッドを適用すると、ターゲット マシン上で 1 回実行でき、上図の結果が得られます。

TMA には CPU の 4 つのカテゴリがあり、各カテゴリの割合は消費量の割合として簡単に理解できます。「最良」のケースでは、Retiring (リタイア) の割合は 100%、残りの割合は 0%、つまり、残りの 3 つのカテゴリが CPU の非効率を引​​き起こすカテゴリになります。

TMA の結果を分析するときは、第 1 レベルで最も割合が高い分岐にのみ注目し、次にレベルごとにトレースしていきます。たとえば、第 1 レベルが Backend であることがわかっている場合は、メモリ部分に注目するだけで済みます。最終的に、CPU 効率の低さの主な原因は、CPU が DRAM にアクセスするパスであることがわかります。

業界ベンチマーク ベンチマーク - SPEC CPU など

レイテンシ遅延

マルチスレッドの最適化において最も懸念される問題はロックです。

TMA は従来のホットスポット分析手法と同じレベルではなく、マイクロアーキテクチャ レベルでソフトウェアのパフォーマンスを分析します。

 どうやって

CPU 内にはフロントエンドとバックエンドがあり、フロントエンドは主に値のフェッチやデコードなどの順次実行を担当し、バックエンドはフロントエンドからの命令の受け取り、順不同での実行、順次リタイアを担当します。

CPU にパフォーマンスのボトルネックがある場合、それはフロントエンドまたはバックエンドに現れるか、分岐予測によって引き起こされる可能性があります。

TMA の最初のレベルには、フロントエンド、バックエンド、投機の 3 つのカテゴリとリタイアがあり、リタイア カテゴリは理想的な状態でのパイプライン実行の割合を表します。

パイプライン スロット: 4 つの放出 CPU がある場合、つまり、フロントエンドが各サイクルで 4 つの uop をバックエンドに送信し、バックエンドも各サイクルで 4 つの uop を受信できる場合、パイプラインは、uop で満たされた後、パイプライン スロットと呼ばれます。

 上の図では、パイプライン スロットは 50% しか埋まっていません。つまり、バブルの 50% が表示されているため、リタイアのカテゴリは 50% になる可能性があります (不正な推測エラーの推測がある場合、リタイアのカテゴリは <50% になります)。

 このようにして、TMA の最初のレベルは、パイプライン スロットを分割して、完全に使用されているパイプライン スロットの数を確認することであることが大まかに理解できます。

未使用のパイプライン スロットをさらに分割するにはどうすればよいですか?

TMA のアイデア: まず、CPU のフロントエンドとバックエンドの接続点、つまり発行されたイベントのステータスを確認します。各サイクルと各パイプラインは、uop が発行された状態と uop が発行されていない状態の 2 つの状態に分割されます。

uop が発行されない場合は、フロントエンドまたはバックエンドに問題があることを意味するため、バックエンドがブロックされているかどうかを確認するだけで、対応する分割を行うことができます。

問題の成功した uop の場合、分岐予測エラーにより後続の uop がリタイアされない可能性があります。つまり、無駄な作業が行われたことになります。

UOP が発行されず、バックエンドがブロックされていない場合 (バックエンド ストール)、フロントエンドの障害 (フロントエンド バウンド) が原因である可能性があります。

TMA は、サイクルごとに CPU の特定の状況を検出する必要はなく、完全なテスト プログラムを実行し、TMA を実行するプログラムの特定のパフォーマンス カウンタ値を取得するだけで済みます。

何らかの統計結果が得られたときに、なぜ第 1 レベルの除算を実行できるのでしょうか?

Total Slots (パイプライン スロットの合計数) は、プログラムの実行サイクルと CPU の 1 サイクルあたりの起動数を乗算することで取得できます。

Slots Retired、つまり、正常にリタイアされた uops の数です。このカウンタは、ほとんどの CPU に実装されています。必要なのは、このサイクルでリタイアされた uop の数をサイクルごとに加算することだけです。

発行されたスロット、つまり合計で正常に起動された uop の数。これはほとんどの CPU にも実装されています。

Fetch Bubbles、つまり、バックエンドの輻輳がないときにフロントエンドが有効に活用していないパイプライン スロットの数をカウントするだけで済みます。

リカバリ バブルとは、リカバリによるフロント エンドのボトルネックによりバック エンドで輻輳が発生していないときに起動されなかった UOP の数を指します。

フェッチ バブルとリカバリ バブルの合計がフロントエンド バブル、つまり、バックエンドで輻輳がないときにフロントエンドによって無駄になるパイプライン スロットの量です。

CPU で対応する論理回路を設計し、パフォーマンス監視ユニット (PMU) を追加することで、上記の式で必要な統計値を簡単に取得できます。

すべてのパイプライン スロットに対するフェッチによって発生するバブルの割合は、フロント エンドによって無駄にされるスロットの割合です。

発行された uops の合計数から、リタイアされた uops の合計数を引きます。これは、問題は発生したがリタイアされなかったにも関わらず役に立たない作業を行った uop の数と、リカバリ プロセス中に発生したバブルを加えたものです。次に、この 2 つの合計をスロットの合計数で割ります。これは、不正な投機によって無駄になったスロットの割合を表すことができます。

uops: 低レベルのハードウェア操作であるマイクロ命令。CPU フロントエンドは、アーキテクチャ命令で表されるプログラム コードを取得し、それを 1 つ以上の uops にデコードする責任があります。

パイプライン スロット: uop の処理に必要なハードウェア リソースを示します。

パイプライン幅: パイプライン スロットの数を示します。

フロントエンド バウンドやバックエンド バウンドなど、パイプライン スロットによって測定されるトップダウン メトリクスは、さまざまな理由 (フロントエンドの問題やバックエンドの問題など) によりブロックされたパイプライン スロットの割合を示します。

フロントエンドとバックエンドの分割: 一般に、(マイクロオペレーション) uops キューの後ろの部分はバックエンドと呼ばれ、マイクロオペレーション キュー モジュールの後続の帯域幅が CPU 起動幅になります。

最新のアウトオブオーダー実行 CPU で使用されるいくつかのメカニズム:

パイプライン化された

スーパースカラー スーパースカラー アーキテクチャ

OOOの実行 

憶測

複数のキャッシュ マルチレベルキャッシュ

メモリのプリフェッチと曖昧さ回避 メモリのプリフェッチと曖昧さ回避

ベクトル演算 ベクトル演算

 フロントエンド---フロントエンド

 フロントエンドバウンドは次のように分割できます

レイテンシ遅延。フロントエンドの過剰なレイテンシが原因でバックエンドに UOP が送信されなかった割合を示します。

帯域幅 帯域幅。これは、フロントエンドのデコード能力が不十分なため、フロントエンドが 4 つの送信の帯域幅比を最大限に活用できないことを意味します。

次の式は、主に Skylake の TMA 比例係数と式を保存するTMAM 3.5skl_client_ratios.pyファイルから派生しています。

式で使用される PMC イベントの具体的な説明は、インテルのインテル® 64 および IA-32 アーキテクチャ ソフトウェア開発者マニュアル、特に第 3 巻 (3A、3B、3C および 3D): システム プログラミング ガイドの第 19 章パフォーマンス監視イベントに記載されています。

フロントエンドバインド

公式:self.val = EV(" IDQ_UOPS_NOT_DELIVERED.CORE ", 1) /  SLOTS (self, EV, 1)

上の式では、EV 関数は特定のイベントの呼び出しを表し、SLOTS は別の関数です。特定の計算方法については、対応する関数定義を検索して表示できます。フロントエンド バウンドの値は、IDQ_UOPS_NOT_DELIVERED.COREイベントの値をSLOTs 関数の値で割ったものです。SLOTS はパイプラインの実行サイクル全体のスロット数を表しており、具体的な方法としては CPU の動作サイクルにパイプライン幅を乗算する方法があります。

def  SLOTS (セルフ、EV、レベル): Pipeline_Width CORE_CLKS (セルフ、EV、レベル)
を返します。 

IDQ_UOPS_NOT_DELIVERED.COREイベントは何を意味しますか? Intel のマニュアルを参照すると、このイベントの定義を見つけることができます。バックエンドストールがない 場合、フロント エンドからバック エンドに UOP が配信されなかった発行  パイプライン スロットをカウントします。簡単に言うと、バックエンドがストールによってブロックされていない場合、フロントエンドが発行しない uop の数、つまりフロントエンドの理由で無駄になる uop の数です。次に、この値をスロットの合計数で割ります。これがフロントエンド バインドの割合となります。

SLOTS の計算式は、pipeline_Width の定数として 4 として定義されています。これは、パイプライン幅が 4 であるためであり、異なる CPU アーキテクチャに従って変更する必要があるためです。

CORE_CLKS 関数の式。最新の CPU には多くのメカニズムがあるため、コア CLKS はさまざまな状況で考慮する必要があります。

def  CORE_CLKS (self, EV, level):
return ((EV("CPU_CLK_UNHALTED.THREAD", level) / 2)*(1 + EV("CPU_CLK_UNHALTED.ONE_THREAD_ACTIVE", level) / EV("CPU_CLK_UNHALTED.REF_XCLK", level))) if ebs_mode else(EV("CPU_CLK_UNHALTED.THREAD") EAD_ANY", レベル) / 2) if smt_enabled else CLKS(
self  EV
level  )

def  CLKS (self, EV, level):
return EV("CPU_CLK_UNHALTED.THREAD", level)

ebs_modesmt_enabled の値に応じてコア clks に 3 つの異なる値を取得します。ebs_mode の値は常に FALSE であるため、現在はsmt_enabledの値のみが考慮されます。

SMT - 同期マルチスレッド

IntelではHT(ハイパースレッディング)と呼ばれています。

Intel および AMD CPU では、各コアに最大 2 つのスレッドがあります。つまり、1 つのコアで 2 つのスレッドを同時に実行できます。smt_enabled が True の場合、コア クロックは CPU_CLK_UNHALTED.THREAD_ANY を 2 で割った値になります。

CPU_CLK_UNHALTED.THREAD_ANY イベントの解釈は次のようになります。 物理コア上の少なくとも 1 つのスレッドが停止状態にないときのコア サイクル、つまり、Core 内のスレッドによって実行されたサイクル数ここで 2 で割るのは、ハイパースレッディング中に 2 つのスレッドが 4 回の起動のパイプライン幅を共有するためです。2 つのスレッドが静的に分割される場合、各スレッドのスロット数は 2*Clk になります。

このことから、ハイパースレッディングを有効にすると各論理コアのパイプライン幅が半分になり、SLOTS の数も半分になることが単純に理解できるため、TMA はハイパースレッド化された CPU の解析も可能であることも証明できます。

フロントエンド_レイテンシ

公式:self.val =  Pipeline_Width  * EV(" IDQ_UOPS_NOT_DELIVERED.CYCLES_0_UOPS_DELIV.CORE ", 2) /  SLOTS (self, EV, 2)

ここでのIDQ_UOPS_NOT_DELIVERED.CYCLES_0_UOPS_DELIV.COREイベントの説明は次のとおりです。  リソース割り当てテーブル (RAT) にUOP が配信されない場合のサイクルを スレッドごとにカウントします。つまり、各スレッドが発行する uops を持たないサイクル数です。このサイクル数にパイプライン幅を掛けて、過剰なフロントエンド遅延によって無駄になったスロットの数を取得します。最後に、スロットの数をスロットの合計数で割って、対応する比率を取得します。

したがって、TMAM を適用する場合は、SMT を有効にしないようにする必要があります。SMT が有効になっている場合は、1 つの物理コアで 2 つの論理コアを同時に使用しないようにしてください。これも TMAM の小さな欠点です。

フロントエンド遅延の原因となる可能性がある: ICache_Misses、ITLB_Misses、Branch_Resteers、DSB_Switches、LCP、MS_Switches の 6 つのサブクラス

Icache_misses

1 つ目は ICache_misses で、命令キャッシュ ミスが発生すると、フロントエンドにはデコードするための命令がなくなり、必然的に対応する UOP がバックエンドに送信されなくなり、フロントエンド レイテンシーのボトルネックが発生します。

self.val = (EV(" ICACHE_16B.IFDATA_STALL ", 3) + 2 * EV(" ICACHE_16B.IFDATA_STALL:c1:e1 ", 3)) / CLKS(self, EV, 3)

ここで、L1 IC キャッシュ ミスによるパイプライン ストールのサイクル数を総サイクル数で割って、L1 IC キャッシュ ミスの割合を求めることとして単純に理解できます。 ICACHE_16B.IFDATA_STALLイベントの説明は次のとおりです: L1 命令キャッシュ ミスによりコード ライン フェッチが停止するサイクル 従来のデコード パイプラインは 16 バイト粒度で動作します L1 命令キャッシュの損失により コードライン フェッチが停止するサイクル

ITLB_misses:

ITLB_Misses、考え方は ICache Misses と同じで、Cycles レベルでも計算されます。Slots レベルで計算を続けてみてはいかがでしょうか。もちろん、Slots レベルの利点は Cycles レベルよりもはるかに大きいですが、多くの特定の基礎となるイベントが Cycles レベルにあり、無駄な Slots の数を計算することは困難または不可能です。そのため、現時点では、統計分析は Cycles レベルでのみ行うことができ、より便利で簡潔になります。

公式:self.val = EV(" ICACHE_64B.IFTAG_STALL ", 3) / CLKS(self, EV, 3)

ICACHE_64B.IFTAG_STALLイベントの解釈は次のようになります。 L1 命令キャッシュ タグ ミスによりコード フェッチが停止したサイクル。L1 命令キャッシュ タグの損失によりコード フェッチが停止するサイクル。キャッシュ タグ ミスが発生した場合、対応するタグが TLB 内に見つからず、TLB ミスが発生したことを意味します。タグ ミスによって発生したサイクル数をサイクルの総数で割った値が、対応する割合となります。

支店_遺跡:

統計は、フロントエンドが間違ったブランチから回復するときに、中間で消費されたサイクル数と合計サイクル数の比率です。

他のミスでも統計を繰り返す可能性があります

self.val = (EV(" INT_MISC.CLEAR_RESTEER_CYCLES ", 3) +  BAClear_Cost  * EV(" BACLEARS.ANY ", 3)) /  CLKS (self, EV, 3)

式内の INT_MISC.CLEAR_RESTEER_CYCLES イベントの対応する解釈は次のようになります: 分岐予測ミスまたはマシン クリア イベントに続いて、フロントエンドが リステアされたパスからフェッチするために発行ステージが待機 しているサイクル数、つまり、フロント エンドがリステアを待機するサイクル数。

Resteer は、分岐予測エラーの回復プロセスを指します。

BACLEARS.ANY --- フロントエンド予測エラーの発生数

BAClear_Cost 各分岐予測ミスで無駄になったサイクル数

ブランチ リストアの場合、ブランチ ミス予測マシン クリア、および新しいブランチ アドレス クリアの3 つのカテゴリにさらに拡張できます

DSB_スイッチ:

この分類は、DSB パイプラインから MITE パイプラインに切り替えるときにフロントエンドのストールを引き起こすサイクルの割合をカウントします。

DSBとは、Decoded Stream Buffer の略で、デコードされたいくつかの uOps が格納されます。uops ICahce と理解できます。DSB は、Intel の Sandy Bridge アーキテクチャで導入されました。DSB は、AMD の Op Cache に相当します。名前が示すように、uOps を格納するキャッシュです。

MITEは Micro-instruction Translation Engine の略で、DSB が導入される以前の伝統的なデコード パイプラインです。

フロントエンドが DSB から MITE パイプラインに切り替わるため、特定のペナルティ サイクルが発生し、マイクロオペレーション キューに uops が送信されなくなります。この分類は主に、サイクル全体に対するサイクルのこの部分の割合を計算することを目的としています。

self.val = EV(" DSB2MITE_SWITCHES.PENALTY_CYCLES ", 3) / CLKS(self, EV, 3)

DSB2MITE_SWITCHES.PENALTY_CYCLES イベントは、デコード ストリーム バッファから MITE への切り替えによる遅延のサイクル数、つまり切り替えによって無駄になったサイクル数を示し、この値を合計サイクル数で割って対応する割合を取得します。

LCP:プレフィックスの長さの変更 プレフィックスの長さの変更

デコード中の命令では、動的な長さのプレフィックスが変更されると、数回のサイクル ストール (約 3 サイクル) が発生します。適切なコンパイラ フラグまたはデフォルトのコンパイラを使用すると、LCP 問題が解決される可能性があります。

self.val = EV(" ILD_STALL.LCP ", 3) / CLKS(self, EV, 3)

ILD_STALL.LCP イベントは、  命令のプレフィックス長の変更によってストールが発生したこと、つまり、LCP によって発生したサイクル数を総サイクル数で割ったことを示します。

MS_スイッチ:

私が表現したいのは、DSB パイプラインまたは MITE パイプラインから MS パイプラインへの切り替えで無駄になったサイクルの割合です。

MS はMicrocode  Sequencer マイクロコード シーケンスの略称で、Intel アーキテクチャ図の MSROM モジュールおよび AMD アーキテクチャ図の Microcode Rom に対応します。

self.val = MS_Switches_Cost * EV(" IDQ.MS_SWITCHES ", 3) / CLKS(self, EV, 3)

IDQ.MS_SWITCHES イベントはスイッチング回数をカウントします。具体的な解釈は次のとおりです。 DSB (デコード ストリーム バッファ) またはマイト (レガシー デコード パイプライン) からマイクロコード シーケンスへのスイッチ数。次に、その回数を使用してサイクルの無駄に切り替える ms_switches_cost (ここで MS_SWITCHES_COST は定数 2)。ミリ秒のスイッチングによって発生する一時停止の数を取得できます。最後に、この値をサイクルの合計数で割ります。これが MS スイッチの対応する割合になります。

これが、TMA 方法論が、異なるレベルおよび異なる分類の下での値の比較が無意味であることを強調する理由です。フロントエンド レイテンシー カテゴリが主なボトルネックである場合にのみ、それを拡張します。その後、他のカテゴリを考慮せずに、その下の 6 つのサブカテゴリの割合を比較するだけで済みます。

フロントエンド帯域幅

フロントエンドによって消費されるスロットの割合が帯域幅を十分に活用していない

self.val = self.Frontend_Bound.compute(EV) - self.Frontend_Latency.compute(EV)

つまり、親クラス Frontend_Bound の値から兄弟の Frontend_Latency の値を直接減算します。これら 2 つのサブクラスの合計は、フロントエンドで無駄になっているスロットの総数に等しくなければならないため、単純に減算することで取得できます。次に、帯域幅の 3 つのサブカテゴリを主に詳しく見ていきますが、帯域幅に関しては、TMA は MITE、DSB、LSD の 3 つのサブカテゴリに分割しています。

帯域幅については、TMA は3 つのサブカテゴリに分割しています: MITEDSB、およびLSD . これら 3 つのサブカテゴリは 3 種類のフロントエンド パイプラインに対応しており、問題の uops 幅が 4 ではない可能性があります。

MITE : (Micro-instruction Translation Engine) マイクロ命令翻訳エンジン

フロントエンドのデコーダ パイプラインには非効率性 (無能さ) がある可能性があるため、MITE の帯域幅が不十分になります。

公式:

self.val = (EV(" IDQ.ALL_MITE_CYCLES_ANY_UOPS ", 3) - EV(" IDQ.ALL_MITE_CYCLES_4_UOPS ", 3)) / CORE_CLKS(self, EV, 3)

IDQ.ALL_MITE_CYCLES_ANY_UOPS の場合、カウントは MITE から IDQ (命令デコード キュー) に送信される uops のサイクル数であり、対応する解釈は次のようになります。 サイクル数をカウントします。MITE は少なくとも 1 つの uop が配信されます。

次に、IDQ.ALL_MITE_CYCLES_4_UOPS の場合、統計は 4 つの uop が MITE から IDQ に送信されるときのサイクル数です。つまり、MITE が 4 つの uop に配信されるサイクル数をカウントします。

2 つの差は、MITE の帯域幅が 4 ではない場合のサイクル数であり、この差を合計サイクル数で割って、対応する比率を取得します。もちろん、MITE が 1、2、および 3 uop をそれぞれ IDQ に送信するサイクル数を合計して、帯域幅が 4 未満のサイクル数を取得し、このサイクル数を合計サイクル数で割ると、同じ結果が得られます。

DSB : (デコードされたストリーム バッファ) デコード uop バッファ

非効率 (低効率) の可能性もあります。具体的な理由としては、DSB キャッシュ構造が十分に活用されていないことや、読み取り中にバンク競合が発生したことが考えられます。これらの理由により、DSB が帯域幅を完全に活用できなくなります。具体的な計算式は次のとおりです。

self.val = (EV(" IDQ.ALL_DSB_CYCLES_ANY_UOPS ", 3) - EV(" IDQ.ALL_DSB_CYCLES_4_UOPS ", 3)) / CORE_CLKS(self, EV, 3)

式内の 2 つのイベントの解釈は、観測されるオブジェクトが DSB パイプラインになることを除いて、MITE 式内の 2 つのイベントの解釈と一致しています。2 つのイベントの差は、DSB 効率が低いサイクル数であり、サイクルの総数で割ると、対応する比率が得られます。

しかし、Intel CPU マイクロアーキテクチャのアップグレードに伴い、MITE と DSB の帯域幅もアップグレードされており、最大帯域幅は明らかに 4 ではなくなっていることがわかりました。もう一度 Skylake のアーキテクチャ図を見てみると、DSB の最大帯域幅が6 にMITE の最大帯域幅が 5 に変更されていることがわかります。したがって、ここでの式では、MITE/DSB uops 送信周期から 4 周期以上の uops 帯域幅を引いた値を使用する必要があり、これがより合理的です。1、2、および 3 の uop を送信するサイクル数の合計を分子として使用し、減算の代わりにそれを合計サイクル数で割ります。

フュージョン: フロントエンドが処理のために複数の uop を複雑な uop に融合し、バックエンドの実行時にそれを分割して処理できるようにする Intel CPU のメカニズムです。

LSD : ループストリームディテクタ ループストリームディテクタ

uops 巡回シーケンスを検出して保存します。uops巡回シーケンスが LSD の容量以下の場合、LSD に格納できるため、フロントエンドのデコードなしで対応する uops シーケンスを取得でき、LSD から対応する uops シーケンスを継続的に取り出すだけで済みます。

self.val = (EV(" LSD.CYCLES_ACTIVE ", 3) - EV(" LSD.CYCLES_4_UOPS ", 3)) / CORE_CLKS(self, EV, 3)

LSD.CYCLES_ACTIVEイベントは、LSD によって配信される少なくとも 1 つの uop とデコーダからの配信がない Cycles (LSD には少なくとも 1 つのアップリンクがあり、デコーダにはアップリンク サイクルがない)、LSD.CYCLES_4_UOPS イベントは、LSD によって配信される 4 つの uop とデコーダからの配信がない Cycles (LSD によって提供される 4 つのアップリンク)ラインサイクルを行いますが、デコーダは行いません次に、この 2 つの差は、LSD 帯域幅が十分ではないサイクル数となり、この値を合計サイクル数で割って、対応する比率を取得します。

正しい分析方法は、最初に第 1 層を確認し、次に対応する第 2 層を確認し、次に対応する第 3 層を確認することです。異なるブランチにある各アナロジーの割合に注意を払う必要はなく、同じブランチの割合に注意するだけで済みます。

おすすめ

転載: blog.csdn.net/m0_54437879/article/details/131689069