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のエリアレポートとシステムビューアの結果