インテルのみ表示可能 — GUID: mwh1391807514629
Ixiasoft
5.3. Single Work-Itemカーネルの良いデザイン方法
ポインター・エイリアシングの回避
可能であれば、ポインター引数にrestrictキーワードを挿入します。ポインター引数にrestrictキーワードを含めることにより、オフライン・コンパイラーは、競合しない読み出し動作と書き込み動作の間に不要なメモリー依存関係を作成することを防止します。各反復があるアレイからデータを読み出し、同じ物理メモリー内の別のアレイにデータを書き込むループを検討してください。これらのポインター引数にrestrictキーワードを含めることなく、オフライン・コンパイラーは2つの配列間の依存関係を想定し、その結果としてパイプラインの並列性を少なくします。
「整形式ループの構築
「正常に形成されたループは、整数境界と比較され、反復ごとに1の単純な誘導インクリメントを有する出口条件がありますカーネルに "整形式"ループを含めると、オフライン・コンパイラーがこれらのループを効率的に解析できるため、パフォーマンスが向上します。
次の例は、「整形式ループです。
for (i = 0; i < N; i++) {
//statements
}
次の例は、「整形式ネストループ構造です。
for (i = 0; i < N; i++) {
//statements
for(j = 0; j < M; j++) {
//statements
}
}
ループ運搬依存関係の最小化
以下のループ構造は、各ループ反復が以前の反復によって書き込まれたデータを読み込むため、ループに依存する依存関係を作成します。その結果、前の反復からの書き込み動作が完了するまで、各読み出し動作を続行できません。ループに依存する依存関係が存在すると、オフライン・コンパイラーが達成できるパイプラインの並列性が低下し、カーネルのパフォーマンスが低下します。
for (int i = 0; i < N; i++) {
A[i] = A[i - 1] + i;
}
オフライン・コンパイラーは、ループで静的なメモリー依存分析を実行して、ループが達成できる並列度を判断します。場合によっては、オフライン・コンパイラーが2つの配列アクセス間の依存関係を想定し、その結果としてパイプラインの並列性が低下することがあります。オフライン・コンパイラーは、未知の変数のためにコンパイル時に依存関係を解決できない場合、または配列アクセスが複雑なアドレッシングを伴う場合、ループに依存する依存関係を前提としています。
可能な場合、ループに依存する依存関係を最小限に抑えるため、以下のガイドラインに従ってください。
- ポインター演算を回避します。
カーネルが算術演算から導出されたポインター値を逆参照することによって配列にアクセスするとき、コンパイラーの出力は最適ではない。たとえば、次の方法で配列にアクセスしないでください。
for (int i = 0; i < N; i++) { int t = *(A++); *A = t; }
- 単純な配列インデックスを導入します。
オフライン・コンパイラーがそれらを効率的に解析できないため、次のタイプの複雑な配列インデックスは回避してください。コンパイラー出力が最適ではない場合があります。
- 配列インデックスの非定数。
たとえば、 A [K + i] ( iはループインデックス変数、 Kは未知変数)。
- 同じ添字の場所に複数の索引変数があります。
たとえば、 A [i + 2×j] ( iとjはダブルネストループのループインデックス変数)です。
注: オフライン・コンパイラーは、インデックス変数が異なる下付き文字であるため、効率的に配列インデックスA [i] [j]を解析できます。 - 非線形インデックス付け。
たとえば、 A [i&C]はiがループインデックス変数、 Cが定数または非定数変数です。
- 配列インデックスの非定数。
- 可能であれば、カーネル内の一定の境界を持つループを使用してください。
一定の境界を持つループにより、オフライン・コンパイラーは範囲分析を効果的に実行できます。
複雑なループ終了条件の回避
オフライン・コンパイラーは、終了条件を評価して、後続のループ反復がループパイプラインに入るかどうかを判断します。オフライン・コンパイラーが終了条件を評価するためにメモリーアクセスまたは複雑な動作を必要とすることがあります。このような場合、後続の反復は、評価が完了するまで開始できず、全体的なループのパフォーマンスが低下します。
ネステッド・ループから単一のループへの変換
パフォーマンスを最大にするには、ネステッド・ループを可能な限り単一のフォームに結合します。ネステッド・ループを単一のループに再構成することは、ループ反復間のハードウェア・フットプリントおよび計算オーバーヘッドを低減します。
次のコード例は、ネストされたループの単一ループへの変換を示しています。
ネストループ | 変換シングルループ |
---|---|
|
|
可能な限り最も深いスコープ内の変数の宣言
変数の実装に必要なハードウェア・リソースを減らすには、変数をループで使用する前に宣言します。変数を使用しないループ全体で変数データを保存する必要がないため、できるだけ深いスコープで変数を宣言すると、データの依存関係やハードウェアの使用が最小限に抑えられます。
次の式を検討してみましょう。
int a[N];
for (int i = 0; i < m; ++i) {
int b[N];
for (int j = 0; j < n; ++j) {
// statements
}
}
配列aは、配列bよりも多くのリソースを実装する必要があります。ハードウェアの使用を減らすには、外側ループの反復によってデータを維持する必要がない限り、配列aを内側ループの外側に宣言します。