CUDA プログラミング (4): メモリ管理

メモリー

記憶の基礎知識

一般的に言えば、レジ​​スタ——キャッシュ——メインメモリ——ディスクメモリで、速度は徐々に低下し、容量は徐々に増加します。
ここに画像の説明を挿入
メモリは、プログラマブル メモリと非プログラマブル メモリに分けることができます。プログラマブル メモリとは、ユーザーがこのメモリを読み書きできることを意味し、非プログラマブル メモリとは、ユーザーに公開されていないメモリを指します。

CPU と GPU のメモリ構造では、第 1 レベルと第 2 レベルのキャッシュ (キャッシュ) はプログラム不可能なストレージ デバイスです。

GPU メモリ構造

各スレッドには独自の合計がありresigterslocal memory各ブロックにはそれがあります.このブロック内のすべてのスレッドはそれにアクセスでき、グリッド間には、、、などshared memoryがあり、すべてのグリッドがアクセスできます. メモリが異なれば、スコープ、有効期間、およびキャッシュの動作も異なります。constant memorytexture memoryGlobal memoryCache

メモリー 位置 キャッシュするかどうか アクセス許可 可変寿命
登録 チップ なし デバイスの読み取り/書き込み スレッドと同じ
ローカルメモリ 機内で なし デバイスの読み取り/書き込み スレッドと同じ
共有メモリ チップ なし デバイスの読み取り/書き込み ブロックと同じ
一定の記憶 機内で もつ デバイスは読み取り専用、ホストは読み取り/書き込み プログラムで維持できる
テクスチャーメモリー 機内で もつ デバイスは読み取り専用、ホストは読み取り/書き込み プログラムで維持できる
グローバルメモリ 機内で なし デバイスの読み取り/書き込み、ホストの読み取り/書き込み プログラムで維持できる

登録者を登録する

レジスタは最速のメモリ空間であり、CPU とは異なり、GPU にはより多くのレジスタが予約されています。カーネル関数で変数をそのまま宣言すると、変数はレジスタに格納され、カーネル関数で定義された一定長の配列にもレジスタ内のアドレスが割り当てられます。

レジスタはスレッドごとにプライベートです. レジスタには通常, 頻繁に使用されるプライベート変数が格納されます. レジスタ変数のライフサイクルは, 動作開始から動作終了までカーネル関数と同じです.

レジスタは SM の希少なリソースです.Fermi アーキテクチャではスレッドあたり最大 63 個のレジスタがあり、Kepler アーキテクチャでは最大 255 個のレジスタがあります. スレッドが使用するレジスタの数が少ない場合、常駐スレッド ブロックが多くなります。SM での同時スレッド ブロックが多いほど、効率が高くなり、パフォーマンスと使用率が高くなります。したがって、プログラミング時に使用するレジスタを少なくすることをお勧めします。 . 少ないレジスタを使用します。

スレッド内に変数が多すぎてレジスタがまったく足りず、この時点でレジスタがオーバーフローする場合、ローカル メモリは余分な変数を格納するのに役立ち、効率に非常に悪い影響を与えます。

ローカルメモリ ローカルメモリ

レジスタに格納されているが、カーネル関数によって割り当てられたレジスタ空間に入ることができないカーネル関数の変数は、ローカル空間に格納されます. コンパイラがローカルメモリに格納できる変数は次のとおりです.

  • 不明なインデックスで参照されているローカル配列。
  • 多くのレジスタ空間を占有する可能性がある大きなローカル配列または構造。
  • カーネル レジスタの条件を満たさない変数。

ローカル メモリは、基本的にグローバル メモリと同じストレージ領域にあり、そのアクセスは高レイテンシと低帯域幅が特徴です。

2.0 を超えるデバイスの場合、ローカル メモリは各 SM の L1 キャッシュまたはデバイスの L2 キャッシュに保存されます。

共有メモリ 共有メモリ

カーネル関数で次の修飾子を使用するメモリは、共有メモリと呼ばれます__shared__

各SMには、スレッドブロックによって割り当てられた一定量の共有メモリがあり、共有メモリはオンチップメモリ​​であり、メインメモリと比較して、速度がはるかに速く、遅延が少なく、帯域幅が高い. L1 キャッシュに似ていますが、プログラム可能です。共有メモリを使用する場合、共有メモリの過度の使用によって SM 上のアクティブなワープの数を減らさないように注意してください。

共有メモリはカーネル関数で宣言されており、そのライフサイクルはスレッドブロックと同じです.スレッドブロックが実行を開始すると、このブロックの共有メモリが割り当てられ、スレッドブロックが実行を終了すると、共有メモリが割り当てられます.メモリが解放されます。

共有メモリはブロック内のスレッドから見え、他のスレッド ブロックからはアクセスできないため、競合の問題があり、共有メモリを介して通信することもできます。メモリ競合を避けるために, 同期文を使うことができますvoid __syncthreads();. 同期文は, スレッドブロックが実行されたときの各スレッドの障害点に相当します. ブロック内のすべてのスレッドがこの障害点まで実行されると, 次の計算が可能になります.実行されます。ただし、頻繁に使用すると、カーネルの実行効率に影響します。

共有メモリは、高速並列アクセスのために同じサイズのメモリ ブロックに分割されます。
銀行:分割の方法です。CPUではあるアドレスにアクセスしてそのアドレスのデータを取得することをメモリアクセスといいますが、ここでは一度にバンク数分のアドレスにアクセスし、それらのアドレスのデータをすべて取得し、論理的にマッピングすることを指します異なる銀行に。メモリ読み取り制御に似ています。
同時に高帯域幅のメモリ アクセスを実現するために、共有メモリは、同時にアクセスできる同じサイズのメモリ ブロック (バンク) に分割されます。したがって、メモリ内のn個のアドレスを読み書きする動作は、b個の独立したバンクの同時動作の方法で実行できるので、実効帯域幅はバンクのb倍に増加する。
複数のスレッドによって要求されたメモリ アドレスが同じバンクにマップされている場合、これらの要求はシリアル化 (シリアル化) されます。ハードウェアは、これらの要求を競合することなく x シーケンスの要求に分割し、帯域幅を x 分の 1 に減らします。ただし、ワープ内のすべてのスレッドが同じメモリ アドレスにアクセスすると、ブロードキャスト (ボードキャスト) が生成され、これらの要求は一度に完了します。コンピューティング機能 2.0 以上のデバイスにはマルチキャスト機能もあり、同じワープ内の同じメモリ アドレスに同時にアクセスするいくつかのスレッドの要求に応答できます。

コンスタントメモリ コンスタントメモリ

コンスタント メモリはデバイス メモリに常駐し、各 SM には専用のコンスタント メモリ キャッシュ、コンスタント メモリ使用量があります__constant__

定数メモリは、カーネル関数の外部で宣言され、グローバル スコープで宣言されます. すべてのデバイスに対して、一定量の定数メモリのみを宣言できます. 定数メモリは静的に宣言され、同じコンパイル ユニット内のすべてのカーネル関数に表示されます. 定数メモリは、ホストによって初期化された後、カーネル関数によって変更できません。

テクスチャーメモリー テクスチャーメモリー

テクスチャ メモリは、GPU の一種の読み取り専用メモリです. グローバル メモリの特定のセグメントをテクスチャ メモリにバインドするために使用されます. グローバル メモリのこのセグメントは、通常、1 次元の CUDA 配列/グローバル メモリ、2 次元の形式をとりますまたは 3 次元 CUDA 配列 を使用し、テクスチャ メモリを読み取ることによってグローバル メモリからデータをフェッチします (テクスチャ フェッチとも呼ばれます)。アラインメントとマージを必要とするグローバル メモリ アクセスと比較して、テクスチャ メモリは、アラインされていないアクセスとランダム アクセスに優れた高速化効果があります。

グローバルメモリ グローバルメモリ

グローバル メモリは、GPU コアから独立したハードウェア RAM、つまりビデオ メモリと呼ばれるもので、GPU のメモリ空間のほとんどはグローバル メモリです。グローバル メモリは、GPU で最大のメモリ空間であり、レイテンシが最も高く、最も一般的なメモリを使用します。グローバルとはスコープとライフサイクルのことで、ホスト側のコードで定義するのが一般的ですが、デバイス側でも定義できますが、修飾子が必要です. 破壊されない限り、同じライフに属します.アプリケーションとしてのサイクル。

キャッシュキャッシュ

GPU キャッシュはプログラム不可能なメモリであり、GPU には 4 種類のキャッシュがあります。

  • L1 キャッシュ
  • L2 キャッシュ
  • 読み取り専用定数キャッシュ
  • 読み取り専用のテクスチャ キャッシュ

各 SM には第 1 レベルのキャッシュがあり、すべての SM が第 2 レベルのキャッシュを共有します。第 1 レベルと第 2 レベルのキャッシュの機能は、ローカル メモリとグローバル メモリにレジスタ オーバーフロー データを含むデータを格納するために使用されます。各 SM には読み取り専用の定数キャッシュと読み取り専用のテクスチャ キャッシュがあり、それぞれのメモリ空間からの読み取りパフォーマンスを向上させるためにデバイス メモリで使用されます。

CPU とは異なり、CPU の読み取りおよび書き込みプロセスはキャッシュされる場合がありますが、GPU の書き込みプロセスはキャッシュされず、読み取りプロセスのみがキャッシュされます。

GPU メモリの割り当て、解放、転送

CUDA プログラムは GPU メモリと CPU メモリを使用し、CPU メモリの割り当てと解放は new と delete (C++)、malloc、calloc と free (C) を使用できます。GPU メモリの割り当てと解放は、CUDA が提供するライブラリ関数を使用して実装されます。同時に、両者のメモリは独立しているため、伝送を実現するには別のメモリにデータをコピーする必要があります。

メモリ データの割り当て

\\ 分配设备上的内存。
cudaError_t cudaMalloc(void** devPtr, size_t size)

cudaMallocこの関数は、デバイスにメモリを割り当てるために使用され、ホストによって呼び出される必要があります (つまり、CPU によって実行されるコードで呼び出されます)。その戻り値はcudaError_t、考えられるすべてのエラー状況を列挙する列挙型です。関数呼び出しが成功すると、 が返されますcudaSuccess最初のパラメータの型は でvoid **、割り当てられたメモリの最初のアドレスを指します。2 番目のパラメーターの型は でsize_t、割り当てられるメモリのサイズをバイト単位で指定します。

メモリーデータ解放

\\ 释放先前在设备上申请的内存空间。
cudaError_t cudaFree(void* devPtr)

cudaFreeこの関数は、デバイス上で以前に割り当てられたメモリ空間を解放するために使用されますが、malloc によって割り当てられたメモリを解放することはできません。戻り値の型は still ですcudaError_t関数パラメーターは、解放する必要があるデバイス メモリの最初のアドレスを指します。

メモリーデータ転送

ホスト メモリとデバイス メモリ間のデータ同期転送を完了するには、cudaMemcpy次の関数を使用する必要があります。

\\ 数据同步拷贝。
cudaError_t cudaMemcpy(void* dst, const void* src, size_t count, cudaMemcpyKind kind)

ホスト メモリとデバイス メモリ間の非同期データ転送を完了するには、cudaMemcpyAsync次の関数を使用する必要があります。

\\ 数据异步拷贝
cudaError_t cudaMemcpyAsync(void* dst, const void* src, size_t count, cudaMemcpyKind kind, cudaStream_t stream = 0)

ストリームがゼロ以外の場合、他のストリーム操作と重複する可能性があります。

cudaMemcpyKindデータ送信の方向を示します。次のオプションがあります。

  • cudaMemcpyHostToHost
  • cudaMemcpyHostToDevice
  • cudaMemcpyDeviceToHost
  • cudaMemcpyDeviceToDevice

エラー処理

ほとんどすべての CUDA API 関数は cudaError_t 型の値を返すため、関数呼び出しが成功したかどうかを示すために使用されます。戻り値が cudaSuccess の場合、関数呼び出しは成功です。失敗した場合、戻り値は失敗の特定のコードをマークし、プログラマーは cudaGetErrorString 関数を介して特定のエラー メッセージを取得できます。したがって、コードの美しさを損なうことなくプログラムの堅牢性を高め、エラー修正を容易にするために、GPUAssert()マクロ関数を使用することをお勧めします。
例えば:

GPUAssert(cudaMalloc(&dev_a, sizeof(int)));

おすすめ

転載: blog.csdn.net/weixin_43603658/article/details/129912525