Oracle® Solaris Studio 12.4: パフォーマンスアナライザチュートリアル

印刷ビューの終了

更新: 2014 年 12 月
 
 

キャッシュ競合メトリックとキャッシュプロファイリングメトリックの理解

チュートリアルのこのセクション以降では、正確な dcm ハードウェアカウンタからデータが得られた実験が必要です。正確な dcm カウンタをサポートしないシステムで行われた実験は、これ以降のチュートリアルで使用できません。

dcm カウンタはキャッシュミスをカウントしています。キャッシュミスとは、キャッシュにないメモリーアドレスを参照するロードおよびストアのことです。

次のいずれかの理由により、アドレスがキャッシュに存在しない場合があります。

  • 現在の命令が、その CPU からそのメモリー位置への最初の参照であるため。より正確には、現在の命令が、キャッシュ行を共有するメモリー位置のいずれかへの最初の参照であるため。

  • スレッドがほかのメモリーアドレスを多く参照しすぎて、現在のアドレスがキャッシュからフラッシュされたため。これはキャパシティミスです。

  • 同じキャッシュ行にマップするほかのメモリーアドレスをスレッドが参照したことにより、現在のアドレスがフラッシュされるため。これは競合ミスです。

  • キャッシュ行の内部にあるアドレスに別のスレッドが書き込んだことにより、現在のスレッドのキャッシュ行がフラッシュされるため。これは共有ミスであり、2 種類の共有ミスのいずれかである可能性があります。

    • 真の共有。現在のスレッドが参照しているアドレスと同じアドレスに、ほかのスレッドが書き込みました。真の共有が原因であるキャッシュミスは回避不可能です。

    • 偽共有。現在のスレッドが参照しているアドレスとは異なるアドレスに、ほかのスレッドが書き込みました。偽共有が原因のキャッシュミスが発生する理由は、データ-ワードの粒度ではなくキャッシュ行の粒度でキャッシュハードウェアが動作することです。偽共有を回避するには、適切なデータ構造を変更し、各スレッドで参照される異なったアドレスが異なったキャッシュ行に配置されるようにします。

この手順では、関数 computeB() に影響を及ぼす偽共有のケースを検証します。

  1. 「概要」に戻り、「L1 データキャッシュミス」のメトリックを有効にし、「命令当たりのサイクル数」および「包括的 CPU 合計時間」のメトリックを無効にします。

    image:「L1 データキャッシュミス」カウンタを有効にした「概要」ページ
  2. ふたたび「関数」ビューに切り替え、compute*() ルーチンに注目します。

    image:「L1 データキャッシュミス」のメトリックを示す computeB の「関数」ビュー

    すべての compute*() 関数はほとんど同じ命令カウントを示しますが、computeB() はより高い「排他的 CPU 合計時間」を示し、排他的 L1 データキャッシュミスのカウントが大きい唯一の関数であることを思い出してください。

  3. 「ソース」ビューに戻ると、computeB() ではキャッシュミスが 1 つの行ループに含まれています。

  4. 「ビュー」ナビゲーションパネルで、「詳細ビュー」または「+」ボタンをクリックし、「逆アセンブリ」を選択します。

    「L1 データキャッシュミス」の数値が高い行が見つかるまで、「逆アセンブリ」ビューをスクロールします。それはロード命令です。


    ヒント  -  「逆アセンブリ」などのビューの右マージンに含まれるショートカットをクリックすると、メトリックの高い行 (ホットライン) にジャンプできます。マージンの上部にある「次のホットライン」下向き矢印、またはゼロ以外のメトリックのマーカーをクリックすると、メトリックの値が顕著である行にすばやくジャンプできます。
    image:computeB 関数の「逆アセンブリ」ビュー

    SPARC システムでは、–xhwcprof でコンパイルした場合、ロードおよびストアには、命令がダブルワード (workStruct_t データ構造内の sum_ctr) を参照していることを示す構造情報が注釈として付けられます。アドレスが次の行と同じであり、命令が <branch target> である行も見えます。そのような行は次のアドレスが分岐のターゲットであることを示しており、<branch target> より上の命令を実行することなく、ホットとして指摘された命令にコードが到達した可能性があります。

    –xhwcprof は x86 ではサポートされていないため、x86 システムではロードおよびストアに注釈が付かず、<branch target> 行は表示されません。

  5. 「関数」ビューと「逆アセンブリ」ビューを切り替えながら、さまざまな compute*() 関数を選択します。

    すべての compute*() 関数で、「命令の実行」のカウントが高い命令は同じ構造フィールドを参照します。

computeB() は、同じ数の命令を実行するにもかかわらず、ほかの関数よりも非常に長い時間がかかっており、キャッシュミスが発生する唯一の関数であることがわかりました。キャッシュミスのあるロードはキャッシュヒットするロードよりも計算に要するサイクルが多いため、命令の実行サイクル数の増加はキャッシュミスが原因です。

computeB() を除いたすべての compute*() 関数について、各スレッドから引数で指される構造体 workStruct_t 内のダブルワードフィールド sum_ctr は、そのスレッドに対する Workblk の内部に含まれています。Workblk 構造体は隣接して割り当てられますが、十分な大きさを持っているため、各構造体内のダブルワードはキャッシュ行を共有するには離れすぎています。

computeB() については、スレッドからの workStruct_t 引数はその構造体の連続的なインスタンスであり、その長さは 1 ダブルワードしかありません。結果として、異なるスレッドによって使用されるダブルワードがキャッシュ行を共有し、あるスレッドからのストアが別のスレッド内のキャッシュ行を無効化する原因になります。キャッシュミスカウントの高さはそのことが原因であり、「CPU 時間合計」および「CPU サイクル」メトリックの高さはキャッシュ行の遅延リフィルが原因です。

この例では、スレッドによって格納されるデータワードは重複しませんが、キャッシュ行を共有します。このパフォーマンスの問題は「偽共有」と呼ばれます。スレッドが同じデータワードを参照する場合、それは真の共有です。これまでに見てきたデータは、偽共有と真の共有を区別しません。

偽共有と真の共有の違いについては、このチュートリアルの最後のセクションで調査します。