Intel® FPGA SDK for OpenCL™: ベスト・プラクティス・ガイド

ID 683521
日付 12/08/2017
Public
ドキュメント目次

2.6. HTMLレポートの情報に基づいたOpenCLのデザイン例の最適化

HTMLレポートの情報を活用してOpenCL™カーネルを最適化する方法に関するガイド。

行列の正方形AxAを実行するOpenCLのデザイン例:

// performs matrix square A*A
// A is a square len*len matrix

kernel void matrix_square (global float* restrict A, 
                           unsigned len, 
                           global float* restrict out) {
  for(unsigned oi = 0; oi < len*len; oi++) {
    float sum = 0;
    int row = oi % len;
    for (int col = 0; col < len; col++) {
      unsigned i = (row * len) + col;  // 0, 1, 2, 3, 4,...
      unsigned j = (col * len) + row;  // 0, 3, 6, 9, 1,...
      sum += A[i] * A[j];
    }
    out[oi] = sum;
  }
}

カーネルmatrix_squareのエリアレポートのシステムビューは、 ブロック3のフリップフロップ(FF)とRAMの推定使用量が高いことを示しています。システムビューアのBlock3をさらに調べると、Block3のレイテンシー値も高いことがわかります。

図 26. 最適化されていないカーネルmatrix_squareのエリアレポートとシステムビューアの結果

これらのパフォーマンスのボトルネックの原因は、システムがループ内からグローバルメモリーからデータをロードしているためです。したがって、次の変更されたコードに示すように、データをローカルメモリーにプリロードすることが、最初に行う最適化のステップです。

kernel void matrix_square_v1 (global float* restrict A, 
                              unsigned len, 
                              global float* restrict out)
{
  // 1. preload the data into local memory
  //    - suppose we know the max size is 4X4

  local int cache_a[16];for(unsigned k = 0; k < len*len; k++) {  cache_a[k] = A[k];}

  for(unsigned oi = 0; oi < len*len; oi++) {
    float sum = 0;
    int row = oi % len;
    for(int col = 0; col < len; col++) {
      unsigned i = (row * len) + col;  // 0, 1, 2, 3, 4,...
      unsigned j = (col * len) + row;  // 0, 3, 6, 9, 1,...
      sum += cache_a[i] * cache_a[j];
    }
    out[oi] = sum;
  }
}
図 27. 修正されたカーネルmatrix_square_v1のエリアレポートとシステムビューアの結果

エリアレポートとシステムビューアの結果に示されているように、ローカルメモリーにデータをプリロードすると、RAMの使用量が3分の1に減少し、レイテンシー値が255から97に低下します。

matrix_square_v1のエリアレポートをさらに調べると、以下のエリアレポートの行30であるコードラインint row = oi%lenは、法計算のために異常に大きなエリアが使用されます。

図 28. 修正カーネルmatrix_square_v1のエリアレポートのソースビュー

モジュラス計算を削除して列カウンターに置き換えると、修正されたカーネルmatrix_square_v2に示すように、適応ルックアップテーブル(ALUT)およびFF使用量を50%削減できます。

kernel void matrix_square_v2 (global float* restrict A, 
                              unsigned len, 
                              global float* restrict out)
{
  // 1. preload the data into local memory
  //    - suppose we know the max size is 4X4
  // 2. remove the modulus computation

  local int cache_a[16];
  for (unsigned k = 0; k < len*len; k++) {
    cache_a[k] = A[k];
  }

  unsigned row = 0;
  unsigned ci = 0;
  for (unsigned oi = 0; oi < len*len; oi++) {
    float sum = 0;

    // keep a column counter to know when to increment rowif (ci == len) {  ci = 0;  row += 1;}ci += 1;

    for (int col = 0; col < len; col++) {
      unsigned i = (row * len) + col;   // 0, 1, 2, 3, 4,...
      unsigned j = (col * len) + row;   // 0, 3, 6, 9, 1,...
      sum += cache_a[i] * cache_a[j];
    }
    out[oi] = sum;
  }
}
図 29. 修正されたカーネルmatrix_square_v1とmatrix_square_v2間のエリア使用の比較

matrix_square_v2のエリアレポートをさらに調べると、インデックスijの計算(つまり、符号なしi =(row * len)+ colおよび符号なしj =(col * len)+ row)ではALUTとFFの使用量の見積もりが非常に異なることがわかります。さらに、エリアレポートは、これらの2つの計算がデジタル信号処理(DSP)ブロックを使用していることも示しています。

図 30. 異なるインデックス計算の異なるエリアの使用を示す修正カーネルmatrix_square_v2のエリアレポート

インデックス計算のためにDSPとRAMブロックの使用を最適化する方法は、乗算計算を削除して、下記の修正されたカーネルmatrix_square_v3に示すように、加算を追跡するだけです。

kernel void matrix_square_v3 (global float* restrict A, 
                              unsigned len, 
                              global float* restrict out) {
  // 1. preload the data into local memory
  //    - suppose we know the max size is 4X4
  // 2. remove the modulus computation
  // 3. remove DSP and RAM blocks for index calculation helps reduce the latency

  local int cache_a[16];
  for (unsigned k = 0; k < len*len; k++) {
    cache_a[k] = A[k];
  }

  unsigned row_i = 0;
  unsigned row_j = 0;
  unsigned ci = 0;
  for (unsigned oi = 0; oi < len*len; oi++) {
    float sum = 0;
    unsigned i, j;

    // keep a column counter to know when to increment row
    if (ci == len) {
      ci = 0;
      row_i += len;
      row_j += 1;
    }
    ci += 1;

    i = row_i;  // initialize i and jj = row_j;for (int col = 0; col < len; col++) {i += 1;       // 0, 1, 2, 3, 0,...j += len;     // 0, 3, 6, 9, 1,...
      sum += cache_a[i] * cache_a[j];
    }
    out[oi] = sum;
  }
}

乗算ステップを削除することで、下記のエリアレポートに示すように、DSP使用率を50%削減できます。さらに、この修正はレイテンシーを短縮するのに役立ちます。

図 31. 修正されたカーネルmatrix_square_v2とmatrix_square_v3間のエリア使用の比較

レイテンシーをさらに削減するために、修正されたカーネルmatrix_square_v3のループ分析レポートを確認することができます。以下に示すように、解析ペインと詳細ペインでは、 sum + = cache_a [i] * cache_a [j]のループに依存する依存関係があるため、Block27にIIボトルネックが発生しています。

図 32. 修正されたカーネルのループ解析matrix_square_v3

ループで運ばれる依存関係を解決するには、修正されたカーネルmatrix_square_v4で強調表示されているコードに示すように、計算の乗算部分と加算部分を分けることができます。

kernel void matrix_square_v4 (global float* restrict A, 
                              unsigned len, 
                              global float* restrict out)
{
  // 1. preload the data into local memory
  //    - suppose we know the max size is 4X4
  // 2. remove the modulus computation
  // 3. remove DSP and RAM blocks for index calculation helps reduce the latency
  // 4. remove loop-carried dependency 'sum' to improve throughput by trading off area

  local int cache_a[16];
  for (unsigned k = 0; k < len*len; k++) {
    cache_a[k] = A[k];
  }

  unsigned row_i = 0;
  unsigned row_j = 0;
  unsigned ci = 0;
  for (unsigned oi = 0; oi < len*len; oi++) {
    float sum = 0;
    unsigned i, j;

    float prod[4];   // make register
    #pragma unroll
    for (unsigned k = 0; k < 4; k++) {
      prod[k] = 0;
    }

    // keep a column counter to know when to increment row
    if (ci == len) {
      ci = 0;
      row_i += len;
      row_j += 1;
    }
    ci += 1;

    i = row_i;  // initialize i and j
    j = row_j;
    for (int col = 0; col < len; col++) {
      i += 1;       // 0, 1, 2, 3, 0,...
      j += len;     // 0, 3, 6, 9, 1,...
      prod[col] = cache_a[i] * cache_a[j];
    }

    sum = prod[0];#pragma unrollfor (unsigned k = 1; k < 4; k++) {  sum += prod[k];}

    out[oi] = sum;
  }
}

以下のエリアレポートおよびシステムビューアの結果に示されているように、計算ステップを分割することで、エリア使用量の増加を犠牲にしてより高いスループットを達成できます。この変更により、ループのII値が1に減少し、レイテンシーが30から24に減少します。

図 33. 修正されたカーネルmatrix_square_v4のエリアレポートとシステムビューアの結果