レイテンシーの長い命令の影響を特定する

インテル® VTune™ プロファイラー

この記事は、インテル® ソフトウェア・ネットワークに掲載されている「Identify Long Latency Instruction Impacts」の日本語参考訳です。



除算や平方根演算のようにレイテンシーの長い命令は、アプリケーション実行中にストールの原因となります。インテル® VTune™ Amplifier XE のパフォーマンス・プロファイラーは、開発者がアプリケーションを解析し、アルゴリズムとマイクロアーキテクチャーのパフォーマンス問題を特定することを支援するパフォーマンス解析ツールです。プロセッサーのパフォーマンス・モニタリング・ユニット (PMU) を使用してプロセッサー・イベントのサンプリングを行い、実行時の演算数を統計的にサンプリングします。

インテル® VTune™ Amplifier XE は、演算の発生箇所と、実行中にその演算によりストールサイクルが発生しているかどうかを特定します。

インテル® Core™2 プロセッサー・ファミリー (インテル® Core™2 Duo/Quad プロセッサーなど)

DIV

実行された除算の数をカウントします。実行された整数の除算、浮動小数点の除算、平方根演算が含まれます。
CYCLES_DIV_BUSY

除算または平方根演算の実行に除算器が費やしたサイクル数をカウントします。除算は整数、X87、またはストリーミング SIMD 拡張命令 (SSE) です。平方根演算は X87 または SSE です。
インテル® Core™ アーキテクチャー Nehalem (インテル® Core™ i7/i5/i3 プロセッサー)

ARITH.DIV

除算または平方根演算の数をカウントします。除算は整数、X87、またはストリーミング SIMD 拡張命令 (SSE) です。平方根演算は X87 または SSE です。
ARITH.CYCLES_DIV_BUSY

除算または平方根演算の実行に除算器が費やしたサイクル数をカウントします。除算は整数、X87、またはストリーミング SIMD 拡張命令 (SSE) です。平方根演算は X87 または SSE です。
インテル® Core™ アーキテクチャー SandyBridge

ARITH.FP_DIV

実行された除算の数をカウントします。
ARITH.FPU_DIV_ACTIVE

除算または平方根演算の実行に除算器が費やしたサイクル数をカウントします。演算ごとに定数 (6) サイクルが追加されます。

表 1: 特定のイベントのカウントに使用されるPMU イベント


例:
N 体問題の例について考えてみましょう。N 体問題は、互いに万有引力で相互作用し合う天体 (星) の集団としての動きを予測します。この例は、一定の時間ステップ範囲において、時間ステップごとにそれぞれの星の引力の総和を計算し、それを基に位置、加速度、および速度を更新します。この実装では、反復ごとに O(N2) 演算が発生します。

runSerialBodies()
{
...
// 一定の時間ステップ範囲のシミュレーションを実行する
for (double s = 0.; s < STEPLIMIT; s += TIMESTEP)
{

  // 天体の加速度を計算する
  for (i = 0; i < n - 1; ++i)
  {
   for (j = i + 1; j < n; ++j)
   {

     // 天体間の距離を計算する
     double dx = body[i].pos[0]-body[j].pos[0];
     double dy = body[i].pos[1]-body[j].pos[1];
     double dz = body[i].pos[2]-body[j].pos[2]; 

     double distsq = dx*dx + dy*dy + dz*dz; 

     if (distsq < MINDIST)distsq = MINDIST; 

     double dist = sqrt(distsq); 

     // j から i の単位ベクトルを計算する
     double ud[3];
     ud[0] = dx / dist;
     ud[1] = dy / dist;
     ud[2] = dz / dist; 

     // F = G*mi*mj/distsq だが F = ma なので ai = G*mj/distsq
     double Gdivd = GFORCE/distsq;
     double ai = Gdivd*body[j].mass;
     double aj = Gdivd*body[i].mass;
     // 単位ベクトルを使用して加速度コンポーネントを適用する
     for (int k = 0; k < 3; ++k)
     {
        body[j].acc[k] += aj*ud[k];
        body[i].acc[k] -= ai*ud[k];
      }
    }
  }

  // 加速度を適用し、次の天体に進む
  for (i = 0; i < n; ++i)
  {
    for (j = 0; j < 3; ++j)
    {
       body[i].vel[j] += body[i].acc[j] * TIMESTEP;
       body[i].pos[j] += body[i].vel[j] * TIMESTEP;
       body[i].acc[j] = 0.;
     }
  }

}
...
}

このサンプルコードをインテル® Core™ i7 (x980) を搭載したシステム (3.33GHz、6 コア + ハイパースレッディング有効) とインテル® VTune™ Amplifier XE で解析した結果を以下に示します。

fig1.png

図 1: サンプルコードの解析結果。クロック数の 85%、ディスパッチされたマイクロオペレーションの 89.3%、そしてディスパッチ・ストールの 83.4% がこのコード領域で発生しています。UOPS_DISPATCHED などの各種イベントの詳細は、インテル® VTune™ Amplifier XE のヘルプを参照してください。

このコードを最適化する 1 つの方法として、以下に示すように除算を逆数の乗算に置換できます。

// j から i の単位ベクトルを計算する
double ud[3];
ud[0] = dx / dist;
ud[1] = dy / dist;
ud[2] = dz / dist;

つまり、上記のコードを下記のように変更した場合、

// j から i の単位ベクトルを計算する
double ud[3];
double dd = 1.0 / dist;
ud[0] = dx * dd;
ud[1] = dy * dd;
ud[2] = dz * dd; 

最適化されたコードでは、クロック数が 4,668,000,000 も減り、ディスパッチ・ストールも 7,448,000,000 サイクルから 3,020,000,000 サイクルに減ります。

fig2.png

図 2: オリジナルと最適化バージョンの比較

ここで紹介した例のように、レイテンシーが突出して長い命令を見つけ、解決策を見つけることができれば、パフォーマンスを大幅に改善することができます。インテル® VTune™ Amplifier XE のイベント・サンプリング機能をぜひ試してください。

編集部追加

インテル® VTune™ Amplifier XE は、アプリケーションの実行パフォーマンスを解析するためのツールです。近年のプロセッサでは、高速化のため様々な機構が組み込まれていますが、その分複雑化しており、適切な性能評価が難しくなっています。処理に時間を要している箇所を見つけたとき、それは"何故か?"が分かれば、適切な処置を行うことができるでしょう。本記事ではインテル® VTune™ Amplifier XE でイベントベース・サンプリングを行うことで、複数の連続した割り算がパフォーマンスに多大な影響を与えていた例を示しています。

インテル® Parallel (C++/Fortran) Studio XE には、インテル® コンパイラーおよびこのインテル® VTune™ Amplifier XE が含まれており、パフォーマンス問題の分析と解決、効果の確認までを総合的に支援します。アプリケーションの高速化を検討されるのであれば、インテル® ソフトウェア開発製品を、是非ともお試しください。お問い合わせや評価版のダウンロードはエクセルソフト株式会社まで。

コンパイラーの最適化に関する詳細は、最適化に関する注意事項を参照してください。

タイトルとURLをコピーしました