インテルのみ表示可能 — GUID: chn1469549457114
Ixiasoft
2.8.3. ローカルメモリー
ローカルメモリーは複雑なシステムです。異なるレベルのキャッシュがある一般的なGPUアーキテクチャとは異なり、FPGAはローカルメモリーをFPGA内部の専用メモリーブロックに実装します。
ローカルメモリー特性
- ポート - ローカルメモリーの各バンクには、デザインが同時にアクセスできる書き込みポートと読み出しポートがあります。
- ダブルポンピング - ダブルポンピング機能により、各ローカル・メモリー・バンクは最大3つのリードポートをサポートします。詳細については、 ダブルポンピングのセクションを参照してください。
ローカルメモリーは複雑なシステムです。異なるレベルのキャッシュがある一般的なGPUアーキテクチャとは異なり、FPGAはローカルメモリーをFPGA内部の専用メモリーブロックに実装します。
カーネルのコードでは、 local型の変数としてローカルメモリーを宣言します。
local int lmem[1024];
Intel® FPGA SDK for OpenCL™ Offline Compilerは、幅、深さ、バンク、レプリケーション、相互接続などのローカルメモリープロパティをカスタマイズします。オフライン・コンパイラーは、コードに基づいてアクセスパターンを分析し、アクセス競合を最小限に抑えるためにローカルメモリーを最適化します。
下の図は、サイズ、幅、深さ、バンク、およびレプリケーションの基本的なローカルメモリープロパティを示しています。
HTMLレポートでは、ローカルメモリーの全体的な状態は最適であると報告されていますが、複製されており、潜在的に非効率的です。
高効率カーネルをデザインするための鍵は、決してストールしないメモリーアクセスを持つことです。この場合、データパス内のすべての同時メモリーアクセスサイトは、競合することなくメモリーにアクセスすることが保証されています。
複雑なカーネルでは、オフライン・コンパイラーは、メモリーアクセスに競合があるかどうかを推測するのに十分な情報がない可能性があります。その結果、オフライン・コンパイラーはローカルメモリーロードストアユニット(LSU)を推論してメモリーアクセスを調停します。しかし、LSUを推論することは非効率を引き起こすかもしれない。詳細については、 ローカルメモリーLSUを参照してください。
オフライン・コンパイラーは、指定した正確なサイズのローカルメモリーを実装するとは限りません。 FPGA RAMブロックは特定のディメンションを持つため、オフライン・コンパイラーはサポートされている次のRAMブロック・ディメンションに切り上げるローカルメモリーサイズを実装します。 RAMブロックの詳細については、デバイス固有の情報を参照してください。
ローカル・メモリー・バンク
ローカル・メモリー・バンクは、デフォルトで最小次元でのみ機能します。複数のバンクを有することにより、同時書込みが可能になる。次の図は、次のローカル変数宣言の実装を示しています。
local int lmem[1024][4];
ループ内の各ローカルメモリーアクセスには、別々のアドレスがあります。次のコード例では、オフライン・コンパイラーは4つの別々のバンクを作成するためにlmemを推論できます。ループはlmem [] []への4つの同時アクセスを可能にし、最適な構成を実現します。
kernel void bank_arb_consecutive_multidim (global int* restrict in,
global int* restrict out) {
local int lmem[1024][BANK_SIZE];
int gi = get_global_id(0);
int gs = get_global_size(0);
int li = get_local_id(0);
int ls = get_local_size(0);
int res = in[gi];
#pragma unroll
for (int i = 0; i < BANK_SIZE; i++) {
lmem[((li+i) & 0x7f)][i] = res + i;
res >> 1;
}
int rdata = 0;
barrier(CLK_GLOBAL_MEM_FENCE);
#pragma unroll
for (int i = 0; i < BANK_SIZE; i++) {
rdata ^= lmem[((li+i) & 0x7f)][i];
}
out[gi] = rdata;
return;
}
local int [4] [128] __attribute __((bank_bits(8,7)、bankwidth(4)));
#define BANK_SIZE 4
kernel void bank_arb_consecutive_multidim_origin (global int* restrict in,
global int* restrict out) {
local int a[BANK_SIZE][128] __attribute__((bank_bits(8,7),bankwidth(4)));
int gi = get_global_id(0);
int li = get_local_id(0);
int res = in[gi];
#pragma unroll
for (int i = 0; i < BANK_SIZE; i++) {
a[i][((li+i) & 0x7f)] = res + i;
res >> 1;
}
int rdata = 0;
barrier(CLK_GLOBAL_MEM_FENCE);
#pragma unroll
for (int i = 0; i < BANK_SIZE; i++) {
rdata ^= a[i][((li+i) & 0x7f)];
}
out[gi] = rdata;
return;
}
結果のメモリーのビューは、最初の例の初期ビューと同じです。しかし、バンク・オンに間違ったビットを指定すると、メモリーアービトレーション・ロジックが変化します。
local int a[4][128] __attribute__((bank_bits(4,3),bankwidth(4)));

コンパイラーがローカルメモリーへのアクセスを別々のアドレスに推論できない場合、ローカルメモリーの相互接続を使用してアクセスを調停し、パフォーマンスを低下させます。
ローカルメモリー複製
ローカルメモリーの複製により、同時に読み出し動作が実行されます。オフライン・コンパイラーは、効率的なローカルメモリーアクセスのためにデザインを最適化して、全体的なパフォーマンスを最大化します。メモリー・レプリケーションは、場合によっては非効率なハードウェアにつながりますが、メモリー・レプリケーションは必ずしもRAMの使用を増加させるとは限りません。
オフライン・コンパイラーが3つ以上のワークグループが同時にローカルメモリーから読み出していることを認識すると、ローカルメモリーをレプリケートします。ローカルメモリーの複製がデザインエリアを大幅に増やす場合、カーネル内の障壁の数を減らすか、またはmax_work_group_size値を大きくして複製のファクタを下げることを検討してください。
ダブルポンピング
デフォルトでは、各ローカル・メモリー・バンクには1つの読み出しポートと1つの書き込みポートがあります。ダブルポンピング機能により、各ローカル・メモリー・バンクは最大3つの読み出しポートをサポートすることができます。
ダブルポンピングを可能にする基本的なメカニズムは、M20Kハードウェアにあります。最初のクロックサイクル中、M20Kブロックはダブルクロックになります。次に、第2のクロックサイクルの間、ポートは多重化されて2つの読み出しポートがさらに形成されます。
ダブルポンピング機能をイネーブルすると、オフライン・コンパイラーはエリア対最大周波数を交換します。オフライン・コンパイラーは、ヒューリスティック・アルゴリズムを使用して最適なメモリー構成を決定します。
ダブルポンピングの利点:
- 1つの読み出しポートから3つの読み出しポートに増加する
- RAM使用量を節約する
ダブルポンピングの短所:
- 冗長ロジックを実装する
- 最大周波数を下げる可能性がある
次のコード例は、8つの読み出しポートと1つの書き込みポートを持つローカルメモリーの実装を示しています。オフライン・コンパイラーは、ダブルポンピングを可能にし、ローカルメモリーを3回複製して、最大9つの読み出しポートをサポートできるメモリー構成を実装します。
#define NUM_WRITES 1
#define NUM_READS 8
#define NUM_BARRIERS 1
local int lmem[1024];
int li = get_local_id(0);
int res = in[gi];
#pragma unroll
for (int i = 0; i < NUM_WRITES; i++) {
lmem[li - i] = res;
res >>= 1;
}
// successive barriers are not optimized away
#pragma unroll
for (int i = 0; i < NUM_BARRIERS; i++) {
barrier(CLK_GLOBAL_MEM_FENCE);
}
res = 0;
#pragma unroll
for (int i = 0; i < NUM_READS; i++) {
res ^= lmem[li - i];
}