利用可能なすべてのプロセッサを効果的に使用するためのソフトウェアの適応は、現代のコンピューティングテクノロジーの新たなマルチコアの未来に照らして最も重要です。 このパスで発生する可能性のある他のすべての障害に加えて、既存のプラットフォームとプロセッサの究極の帯域幅の共有に関連する問題があります。 Intel Core2プロセッサパフォーマンスイベントを正しく使用することにより、システム内で使用可能なすべてのコアを完全に使用するためのパスでアプリケーションを停止する正確な理由を特定できます。
スケーラビリティとは何ですか?
並列コンピューティング環境でのソフトウェアスケーラビリティの問題の調査を開始する前に、用語自体の意味を判断する必要があります。 完全なスケーラビリティとは、アプリケーションが使用するコアの数が増えると、タスクの合計実行時間が直線的に減少することを意味します。 ただし、アルゴリズムの並列実行に関するアムダールの法則に従って、プログラムの合計実行時間は、それに応じて最適化されたコードのセグメントによってのみ短縮できます。 したがって、検索の最初のポイントは、達成されたアルゴリズムの並列度を決定することです。
Intel Core2マイクロアーキテクチャに基づくプロセッサパフォーマンスイベントは、この目的のための便利なメトリックを提供します。 各コアにはパフォーマンスモニタリングユニット(PMU)があり、CPU_CLK_UNHALTED.COREイベントにより、各コアのワークサイクル数を決定できます。 VTune™Performance Analyzerを使用して情報を収集した場合、この数値は、対象のプロセスを実行したすべてのコアについて合計されます。 つまり、この数は、アプリケーションの実行に費やされる有効なサイクルの数です。 この番号を「effective_cycles」と呼びます。
PMUの便利な機能は、各サイクルでイベントの値を特定の数値(cmask)と比較し、それがより大きいか等しい(inv = 0)か、この数値より小さい(inv = 1)かを判断する機能です。 このような条件が指定されている場合、PMUはサイクルが満たされた場合にのみサイクルをカウントします。 ただし、これは汎用メーターでのみ可能です。 たとえば、イベントCPU_CLK_UNHALTED.CORE、INST_RETIRED.ANY、およびCPU_CLK_UNHALTED.REFの固定カウンターは、この操作の対象になりません。 イベントCPU_CLK_UNHALTED.CORE_P(デューティサイクルのカウンタの一般化バージョン)の値が、条件「より小さい」(inv = 1)の数値2と比較される場合、カウンタはすべてのサイクルをカウントします。 すべてのプロセスについてこの数を合計し、システム内のコアの数で割ると、プロセスの合計実行時間が取得されます。 この番号を「total_cycles」と呼びます。
取得した値の正確性を確保するには、BIOSおよびOSでスピードステッピング機能を無効にする必要があります。そうしないと、アンロードされたカーネルが低い周波数で動作し、合計時間の取得値が歪む可能性があります。 Effective_cycles / total_cyclesの比率は、完全にスケーラブルなコードに使用されるコアの数に等しく、完全に一貫性のあるコードに1に等しくなります。 さらに、結果は、すべてのシステムコアのどの部分が実行中に関与したかには依存しません。 ただし、プロセッサがコード内のアクティブな待機サイクルを消費する場合、この手法の値は平準化できるため、待機サイクルを正しく実装する必要があります。 より詳細な分析を行うには、Intel Thread Profilerを使用することをお勧めします。
理想的なスケーラビリティからの逸脱は、コード構造と同期ロジックの機能によって引き起こされる可能性がありますが、システムの総リソースに対する高い競争によって引き起こされる可能性もあります。 この記事の目的は、まさにこのような問題とその解決策を体系的に検索することです。
限られたリソース
まず第一に、並列コンピューティングでの高い競争が可能なリソースのリストを決定する必要があります。 これらには以下が含まれます。
- システムスループット
- メモリ帯域幅
- ソケット間通信帯域幅
- ディスクサブシステムの使用率
- 総キャッシュ量
- データアドレス変換バッファサイズ(DTLB)
- キャッシュラインへの個別アクセス
- 行の共通要素。
- 共有要素のない共有回線(偽共有)
リストの最後のコンポーネントは、スレッドとプロセスの同期がキャッシュラインへのアクセスのブロックに依存するため、他のすべてのコンポーネントとはわずかに異なるパフォーマンスへの影響があります。 この違いは、このリストの他の要素の場合のように、より速い/より大きなキャッシュがスケーラビリティの低下の影響を常に回避できるとは限らないという観点から最も明白です。
これらの制限に対して、アプリケーションをスケーリングするための2つの主なシナリオがあります。これについては、以下で説明します。コア間で一定量の作業を分割し、すべてのコアによって実行される作業量を線形に増やします。 これらの2つのシナリオは、スケーラビリティの問題に関してわずかに異なる意味を持っているため、目的の属性が異なります。
スループット
バストラフィックがパフォーマンスの低下にどのような貢献をするかを理解するには、プログラム実行中に実際にバスに流れるトラフィック(合計および個々のコンポーネントごと)と、分析で使用されるプラットフォームのピーク帯域幅を判断する必要があります。 プラットフォームのピークスループットは、多数のサードパーティの要因に依存します。 これは、ハードウェアプリフェッチャー(ハードウェアプリフェッチャー)の構成、スロット内のRAMスロットの数、タイプ、場所、システムバスの周波数、プロセッサー周波数、キャッシュの一貫性を実現する条件です。 したがって、Intel Core 2に基づくプラットフォームのスループットを決定するためのメトリックは考慮できません。それを決定する唯一の許容可能な方法は、合成スループットテストを実施することです。 TRIADアルゴリズムの単一の長いサイクルは、これらの目的に最も適しているようです。 マルチコア計算のスループット制限はシングルコアとは異なる可能性が高いため、上記のテストでは、スレッドまたは個別のプロセスへの分割のいずれかによる並列カウントをサポートする必要があります。
システムの帯域幅制限は、ほとんどの速度低下要因とは少し異なってパフォーマンスに影響します。 大多数の影響は、パフォーマンスへの影響が対応する待機イベントの数として定義される最後のキャッシュレベルでのキャッシュミスの場合のように、定義するメトリックの増加に比例して増加します。 同じ場合、アプリケーションがプラットフォームのすべてのリソースを使い果たすまで気付かない障壁にぶつかるように、パフォーマンスが低下します。 つまり、システム帯域幅の使用に対するパフォーマンスの依存性は、他のイベントの場合のように、線形よりも段階的である可能性が高くなります。 したがって、たとえば、帯域幅の制限に達したため、メモリへのアクセス時間は非線形に増加します。 同時に計算されるトライアドの数を増やしながら、バスへのアクセスの遅延を測定することにより、この効果を確認できます。 バスアクセス遅延は、(サンプリングではなく)カウントモードのパフォーマンスイベントによって比率として測定できます。
Bus Latency = BUS_REQUEST_OUSTANDING.SELF/(BUS_TRANS_BRD.SELF - BUS_TRANS_IFETCH.SELF)
ほとんどの場合、特にピークスループットの場合、ifetchコンポーネント(命令フェッチ)は重要ではないため、無視できます。
システムバストラフィックに寄与する多くのソースがあります。 Intel Core 2プロセッサパフォーマンスイベントを使用すると、多くの方法を使用して、これらのコンポーネントによる完全なバス負荷と個別のバス負荷の両方を判断できます。 システムバスの飽和状態を判別するのは非常に簡単です。 データ転送に使用されるバスサイクルの一部として表すことができます。
BUS_DRDY_CLOCKS.ALL_AGENTS/CPU_CLK_UNHALTED.BUS
または、キャッシュラインの一部としてバスを介して転送されるバイト数として、直接:
Cacheline_Bandwidth (/) ~ 64*BUS_TRANS_BURST.ALL_AGENTS*core_freq / CPU_CLK_UNHALTED.CORE
この場合の便利なメトリックは、サイクルあたりのキャッシュラインの数です。このアプリケーション/ストリームのN個の並列バージョンの欲求は、この値からN回になるためです。 したがって、プラットフォームの制限がこれらの値で定義されている場合、並列カウントで最も可能性の高いバス負荷は、単一のストリームの分析中に簡単に決定できます。
BUS_TRANS_ *イベントを階層的に使用して、バスコンポーネントを分離できます。 それらの簡単な説明は以下の表に示されており、VTuneパフォーマンスアナライザーのオンラインヘルプでも非常に詳しく説明されています。
イベント | 説明 |
BUS_TRANS_ANY | バス上のすべてのトランザクション:Mem、IO、Def、Partial |
BUS_TRANS_MEM | すべてのキャッシュライン、部分的および無効 |
BUS_TRANS_BURST | すべてのキャッシュライン:Brd、RFO、WB、Combined Write |
BUS_TRANS_BRD | すべてのキャッシュライン読み取り:データ、Ifetch |
BUS_TRANS_IFETCH | すべての命令キャッシュライン |
BUS_TRANS_RFO | 排他的使用を要求するときの合計キャッシュライン |
BUS_TRANS_WRB | すべてのキャッシュラインエントリ(変更されたキャッシュライン) |
プラットフォームの制限に達した場合に適用できるバストラフィックを削減する多くの標準的な方法があります。 これらには次のメソッドが含まれます。
- キャッシュ内にあるすべてのキャッシュラインのすべてのバイトを使用する
- コンパイラーによるアライメントを回避するために、サイズの降順で構造の要素を配置する必要があります
- オブジェクトの向きやテーマの一貫性のためではなく、実際の使用によって構造を定義します
- 配列のクロスリーディング次元は、ネストされたループの最も内側にある必要があります
- 可能な限り、構造のコンポーネントをキャッシュします
- 隣接する共有構造コンポーネント
- 大規模な割り当てサイクルには、キャッシュを通過するストリーミング命令を使用します
- 未使用のキャッシュラインをプリフェッチしないでください
- 可能な限り、帯域幅に達するサイクルとプロセッサパフォーマンスに達するサイクルを組み合わせる必要があります。
これは通常、サイクルの反復回数が等しい場合に簡単に達成できますが、原則として異なる反復回数で実行可能です - キャッシュ内にある間、データの使用を最大化するために、データをブロックに分割してみてください。
上記は、可能なオプションの網羅的なリストであると決して主張しておらず、むしろ最も明白なリストです。 実際に試してみた人は誰でも、後者は一般的に言うより簡単だと言うでしょう。
複数文字と多少の混乱をおIびしますが、この資料は本当にやや乾燥しています。 昨年翻訳を行ったので、関連性が失われたことをおpoびします。
記事の続き:
パート2 、
パート3 、
パート4