序文
Du 先生がtensorRT 高パフォーマンス導入コースをゼロから立ち上げたので、以前読んだことがありますが、メモを取っていなかったので、多くのことを忘れていました。今度はもう一度やって、メモを取ります。
このコースでは、合理化された CUDA チュートリアル - 共有メモリを学びます
コースの概要は以下のマインドマップで確認できます
1. 共有メモリ
共有メモリ (shared_memory) については、次のことを知っておく必要があります。
- 共有メモリが計算機に近いため、アクセス速度が速くなります(近いほど速く、近いほど高価になります)。
- 共有メモリは通常、グローバル メモリにアクセスするためのキャッシュとして使用できます。
- 共有メモリを使用してスレッド間の通信が可能
- 通常、
__syncthreads()
同時に表示されます。この関数は、ブロック内のすべてのスレッドを同期し、ダウンする前にこの行まですべて実行されます。- 一般的な方法は、スレッド ID が 0 のときにグローバル メモリから値を取得し、次に syncthreads を使用して、
2. 共有メモリの場合
共有メモリの場合のmain.cppサンプル コードは次のとおりです。
#include <cuda_runtime.h>
#include <stdio.h>
#define checkRuntime(op) __check_cuda_runtime((op), #op, __FILE__, __LINE__)
bool __check_cuda_runtime(cudaError_t code, const char* op, const char* file, int line){
if(code != cudaSuccess){
const char* err_name = cudaGetErrorName(code);
const char* err_message = cudaGetErrorString(code);
printf("runtime error %s:%d %s failed. \n code = %s, message = %s\n", file, line, op, err_name, err_message);
return false;
}
return true;
}
void launch();
int main(){
cudaDeviceProp prop;
checkRuntime(cudaGetDeviceProperties(&prop, 0));
printf("prop.sharedMemPerBlock = %.2f KB\n", prop.sharedMemPerBlock / 1024.0f);
launch();
checkRuntime(cudaPeekAtLastError());
checkRuntime(cudaDeviceSynchronize());
printf("done\n");
return 0;
}
共有メモリの場合のmain.cppサンプル コードは次のとおりです。
#include <cuda_runtime.h>
#include <stdio.h>
//demo1 //
/*
demo1 主要为了展示查看静态和动态共享变量的地址
*/
const size_t static_shared_memory_num_element = 6 * 1024; // 6KB
__shared__ char static_shared_memory[static_shared_memory_num_element];
__shared__ char static_shared_memory2[2];
__global__ void demo1_kernel(){
extern __shared__ char dynamic_shared_memory[]; // 静态共享变量和动态共享变量在kernel函数内/外定义都行,没有限制
extern __shared__ char dynamic_shared_memory2[];
printf("static_shared_memory = %p\n", static_shared_memory); // 静态共享变量,定义几个地址随之叠加
printf("static_shared_memory2 = %p\n", static_shared_memory2);
printf("dynamic_shared_memory = %p\n", dynamic_shared_memory); // 动态共享变量,无论定义多少个,地址都一样
printf("dynamic_shared_memory2 = %p\n", dynamic_shared_memory2);
if(blockIdx.x == 0 && threadIdx.x == 0) // 第一个thread
printf("Run kernel.\n");
}
/demo2//
/*
demo2 主要是为了演示的是如何给 共享变量进行赋值
*/
// 定义共享变量,但是不能给初始值,必须由线程或者其他方式赋值
__shared__ int shared_value1;
__global__ void demo2_kernel(){
__shared__ int shared_value2;
if(threadIdx.x == 0){
// 在线程索引为0的时候,为shared value赋初始值
if(blockIdx.x == 0){
shared_value1 = 123;
shared_value2 = 55;
}else{
shared_value1 = 331;
shared_value2 = 8;
}
}
// 等待block内的所有线程执行到这一步
__syncthreads();
printf("%d.%d. shared_value1 = %d[%p], shared_value2 = %d[%p]\n",
blockIdx.x, threadIdx.x,
shared_value1, &shared_value1,
shared_value2, &shared_value2
);
}
void launch(){
demo1_kernel<<<1, 1, 12, nullptr>>>();
demo2_kernel<<<2, 5, 0, nullptr>>>();
}
ランニング効果は以下の通りです。
main 関数では、cudaGetDeviceProperties
関数を呼び出して現在のデバイスのプロパティを取得し、デバイスの共有メモリのサイズ (通常は 48KB) を出力します。
上記のサンプル コードでは、共有メモリの使用例を 2 つ示しますdemo1_kernel
。demo2_kernel
demo1_kernel
:
この例は主に静的シェア変数と動的シェア変数のアドレスを示すために使用されます。この例では、2 つの静的共有変数と 2 つの動的共有変数を使用します。どちらもカーネル関数の内部または外部で定義でき、制限はありません。開始したカーネル関数にはスレッド ブロックが 1 つだけあり、各スレッド ブロックにはスレッドが 1 つだけあるため、1 つのスレッドだけがカーネル関数を実行し、対応する共有変数のアドレスを出力します。
print ステートメントを通して、静的共有変数のアドレスは順次増加するのに対し、動的共有変数のアドレスは常に同じであることがわかります。
demo2_kernel
:
この例では、図 2-2 に示す。
shared_value1
この例では、2 つの共有変数とを定義しますshared_value2
。開始するカーネル関数には 2 つのスレッド ブロックがあり、各スレッド ブロックには 5 つのスレッドがあり、各スレッド ブロックの最初のスレッドは共有変数 の代入操作を実行します。このステップに到達すると__syncthreads()
、各ブロックの他の 4 つのスレッドは、最初のスレッドが割り当て操作を完了するまで待機します。
最初のブロックに対応するシェア変数には 123 と 55 の値が割り当てられ、2 番目のブロックに対応するシェア変数には 331 と 8 の値が割り当てられます。共有メモリはブロック レベルで共有されるため、2 番目のブロック実行結果からわかるように、1 つのブロック内のすべてのスレッドによって出力されるシェア変数の結果は 123 と 55 であり、2 番目のブロックのすべてのスレッドによって出力されるシェア変数の結果は 331 と 8 です。
共有メモリの例では、共有メモリの使用の基本概念と使用法を示します。共有メモリは、同じスレッド ブロック内のスレッド間でデータを共有でき、低遅延、高帯域幅という特徴があります。実際の CUDA プログラムでは、メモリ アクセス効率の向上や協調アルゴリズムの実装のために共有メモリがよく使用されます。
3. 補足知識
共有メモリに関する知識ポイント: ( Du 先生より)
- sharedMemPerBlock は、ブロック内で使用可能な最大共有メモリを示します
- したがって、ブロック内のスレッドが相互に通信できるようにすることができます。
- sharedMemPerBlockの適用例は以下の通りです
- 共有メモリはオンチップ メモリであり、コンピューティング ユニットに近いため、globalMem よりも高速で、通常はキャッシュとして使用できます。
- データはまずsharedMemに読み込まれ、各種計算を行う際にはglobalMemではなくsharedMemを使用します。
demo_kernel<<<1, 1, 12, nullptr>>>()
; 3 番目のパラメータ 12 は、動的共有メモリのサイズを指定します。
- Dynamic_shared_memory 変数は次で
extern __shared__
始まる必要があります- 不定のサイズの配列として定義されます []
- 12 の単位はバイトです。つまり、3 つの float を安全に格納できます。
- 変数を関数の外に置くことは関数内に置くことと同じです
- そのポインタは、cuda スケジューラの実行時に割り当てられます。
- 静的に割り当てられた共有メモリとしての static_shared_memory
- extern を追加しないでください
__shared__
。- 配列のサイズは定義時に指定する必要があります。
- 静的に割り当てられたアドレスは、動的に割り当てられたアドレスよりも低くなります
- 動的共有変数は、いくつ定義してもアドレスは同じです
- 静的シェア変数、複数のアドレスを定義して重ね合わせる
- 構成されたすべての種類の共有メモリの合計がsharedMemPerBlockより大きい場合、カーネル関数実行エラー、無効な引数
- 異なるタイプの静的シェア変数定義のメモリ分割は必ずしも連続的ではありません
- 中間にメモリ調整ポリシーがあるため、最初の変数と 2 番目の変数の間にギャップが生じる可能性があります。
- したがって、変数間にギャップがある場合、そのギャップは共有メモリより小さい可能性があり、エラーが報告されます。
要約する
このコースでは共有メモリの使い方を学びました. 共有メモリはスレッドブロック内のデータを共有することができます. 計算機に近いため, グローバルメモリよりもアクセス速度が速くなります. 共有メモリは通常、ブロック内のすべてのスレッドを同期するために__syncthreadsと同時に発生します。共有メモリ変数には静的変数と動的変数があり、静的共有メモリ変数は複数のアドレスを定義して重ね合わせますが、動的共有変数はいくつ定義してもアドレスは同じです。定義されたシェア変数には初期値を与えることができず、スレッドまたはその他の手段で割り当てる必要があります。