並列計算コース復習ノート

1 復習課概要

第一章 アムダールの法則 法則の理解(タスクが変わらない場合の速度向上、加速比)、加速の限界

応用問題 6’*5

グリッドとスレッドブロックの配置、グローバルIDの計算 並列、並行、ワープ、グローバルID、CPUマルチコアとGPUマルチコア

プログラム分析問題 10*2

コードの結果を書く、なぜその結果になるのか分析する

CPUマルチコア 10*2

データ分割:各部分が処理するデータ範囲を明確にする タスク並列:スレッドプール実験

CUDAプログラミング 15*2

具体的な問題、グリッドとスレッドブロックを設計する、またはスレッドブロックが与えられている場合はグリッドを設計する; メイン関数の固定フロー;カーネル関数を書くことが鍵;

2 並列計算

2.1 並行と並列

直列:単一マシン単一コア、命令順序実行。

並行:単一マシン単一コア、命令が時間的に並行して実行され、同じ時間間隔で発生する。

並列:単一マシン多コア、または多マシン単/多コア、命令が空間的に並行して実行され、同じ瞬間に発生する。

並列計算は並列コンピュータまたは分散システムなどの高性能計算システムで行われるスーパーコンピューティングです。並列計算は単一問題の解決時間を短縮し、解決規模と精度を向上させ、スループットを向上させることができます。

三つの分類:

  • 計算モード:時間並列(パイプライン)、空間並列(マルチプロセッサ)
  • プログラムロジック:タスク並列、データ並列
  • 応用角度:計算集約、データ集約、ネットワーク集約

2.2 フリンの分類法

命令ストリーム(instruction stream)とデータストリーム(data stream)の実行方式に基づいて並列コンピュータアーキテクチャを分類する方法。 SISD(初期の直列機)、SIMD(単一コアコンピュータ)、MISD(ほとんど使用されない)、MIMD(マルチコアコンピュータ、並列)を含む;

2.3 アムダールの法則

タスク数が一定であると仮定し、計算性能の加速比を通じて、プログラムの中で並列化できない部分が全体のプログラムの性能向上を制限するという法則を明らかにしています。 $$S=\frac{W_{s}+W_{p}}{W_{s}+W_{p}/p}$$ ここで$W_{s}$は直列タスク数、$W_{p}$は並列タスク数、$p$はプロセッサ数、$S$は加速比です。 直列成分の割合$f=W_{s}/W$に基づいて、上式を同時に$W$で除算すると以下の式が得られます: $$S=\frac{f+(1-f)}{f+\frac{1-f}{p}} =\frac{p}{1+f(p-1)}$$ $\lim_{x\rightarrow \infty}S=1/f$、プロセッサ数が無限に増加するとき、システムが達成できる加速比はプログラム中の直列部分によって制限されます。

1
2
3
1.ある直列アプリケーションで、20%の割合が直列実行される必要があります。現在、3倍の性能向上を実現する必要があります。この目標を達成するためには、何個のCPUが必要ですか?5倍の加速比を実現するためには、何個のCPUが必要ですか?
2.5台のコンピュータで実行されている並列プログラムで、10%の並列部分があります。1台のコンピュータでの直列実行に対して、加速比はどれくらいですか?加速比を2倍にしたい場合、何個のCPUが必要ですか?
3.並列化できない部分が5%のアプリケーションを並列プログラムに変更します。現在、市場には2種類の並列コンピュータがあります:コンピュータXは4個のCPUを持ち、各CPUは1時間以内にこのアプリケーションを実行できます。コンピュータYは16個のCPUを持ち、各CPUは2時間以内にこのアプリケーションを実行できます。実行時間を最小化する必要がある場合、どのコンピュータを購入すべきですか?

3 CUDA概説

3.1 異種計算

GPUの並列計算は異種計算の一種で、ホスト側(CPU)とデバイス側(GPU)に分かれており、両者の関係は決して平等ではありません。CUDAはコードがどこで実行されるべきかを明確に示す必要があります。

3.2 CPUとGPUの違い

直感的に言えば、CPUはより多くのリソースをキャッシュと制御フローに使用し、GPUはより多くのデータ計算に使用します。

  1. GPU環境下では、GPUのコアがすべての計算タスクの実行を担当しますが、作業命令は常にCPUから来ます
  2. GPUの場合、GPUコアはデータを自分で取得することはなく、データは常にCPU側から来て、計算結果は再びCPU側に送られます。したがって、GPUはバックグラウンドで計算アクセラレータとしての役割を果たし、CPUのためにいくつかのアウトソーシングタスクを完了します。
  3. このタイプのアーキテクチャは、大量の並列処理ユニットがある場合にのみ非常に効果的です。たとえば、2つまたは4つしかない場合には効果的ではありません。
  4. スレッド束の概念はGPUのアーキテクチャに大きな影響を与えます。データは常に同じサイズのデータブロック単位でGPUに入力される必要があります。データブロックは半分のスレッド束、すなわち16個の要素です。
  5. データは半分のスレッド束のサイズでGPUコアに転送される必要があります。これは、データをGPUに入力するストレージシステムが毎回16個のデータを入力する必要があることを意味します。これには、16個の数を同時に転送できる並列ストレージサブシステムが必要です。これが、GPUのDRAMストレージがDDR5で構成されている理由です。それは並列ストレージだからです
  6. GPUコアとCPUコアは完全に異なる処理ユニットであるため、異なるISA(命令セットアーキテクチャ)を持っていることが予想されます。すなわち、彼らは異なる言語を話しています。

GPUスレッドとCPUスレッドも異なり、作成のオーバーヘッドが非常に低いです。CPUは多段キャッシュを通じて遅延を縮小し、GPUはパイプラインを通じてスループットを向上させることで遅延を縮小します。 設計目標の違いにより、CPUはさまざまなデータタイプを処理するために非常に強力な汎用性を必要とし、論理判断は多数の分岐ジャンプと割り込みの処理を引き起こします。一方、GPUはタイプが統一され、相互に依存しない大規模なデータと中断されない純粋な計算環境に直面しています。

image.png

3.3 CUDAスレッド組織形式

image.png

Thread:並列の基本単位

Thread Block:互いに協力するスレッドグループで、1次元、2次元、または3次元で最大1024個のスレッドを含むことができます。

Grid:1次元、2次元、または3次元で組織されたスレッドブロックのグループで、グローバル変数を共有できます。

Kernel:GPU上で実行されるコアプログラム One kernel One Grid

3.4 CUDAホスト/デバイスプログラミングモデル

3.4.1 関数修飾子

  • _ device _:デバイス側で実行され、デバイス側からのみ呼び出すことができ、デバイス側のサブ関数として使用されます。
  • _ host _:ホスト側で実行され、ホスト側からのみ呼び出すことができ、一般的なC関数と同じです。__global__と同時に使用することはできませんが、__device__と一緒に使用することができ、この場合、関数はデバイスとホストの両方でコンパイルされます。
  • _ global _ :カーネル関数で、デバイス上で実行されますが、ホスト側から呼び出す必要があります。

3.4.2 CUDAカーネル関数の制限

  1. デバイスメモリにのみアクセス可能
  2. void型を返す必要がある
  3. 可変数の引数をサポートしない
  4. 静的変数をサポートしない
  5. 明示的な非同期動作を示し、ホストはカーネルの実行を待たずに次のステップを実行する

3.5 並列計算モデルSIMT

スレッドブロックはプログラムを開始する基本単位であり、スレッド束はプログラムを実行する単位です;

たとえば、ブロックサイズが256スレッドであると言う場合、それはスレッドブロックサイズが8スレッド束であることを意味します。各スレッド束は常に32スレッドを含みます。このパラメータは、プログラムを開始するときに、各スレッドブロックに256スレッドがあることを示していますが、それらがすぐに実行されるわけではないことを意味します。つまり、これらの256スレッドは同時に実行されるわけではありません。代わりに、GPUの実行ハードウェアは8スレッド束を使用してこれらのスレッドを実行します。

SIMTはSIMDの範疇に属します。なぜなら、それも複数のデータ上で同じ命令を実行するからです。ただし、SIMTはユーザーによってスレッドを割り当てることを許可しており、具体的にはCUDAが各スレッドに識別子(番号)を指定しています。

重要な違いの1つは、SIMDは同じベクトル内のすべての要素が統一された同期グループで一緒に実行される必要があるのに対し、SIMTは同じスレッド束に属する複数のスレッドが独立して実行されることを許可していることです。したがって、SIMTはスレッドレベルの並行性を許可し、統一されたスレッド束の下でスレッドが同時に異なることを行うことができます。

3つの違い:

  • 各スレッドには独自の命令アドレスカウンタがある
  • 各スレッドには独自のレジスタ状態がある
  • 各スレッドは独立した実行パスを持つことができる

3.6 GPUアーキテクチャ

ストリームマルチプロセッサSM

1つのスレッドブロックは1つのSMにのみスケジュールされますが、1つのSMは複数のスレッドブロックに対応できます。

SMが計算する1つ以上のスレッドブロックを指定すると、これらのスレッドブロックは複数のワープに分割され、スケジュールを待ちます。

スレッド束内のスレッドは異なるデータ上で同じ命令を実行します。

SMがスレッドブロックを収容する数は、SM内の共有メモリとレジスタ、およびスレッドが使用するリソースに依存します。

スレッドブロック内のすべてのスレッドは論理的に並行して実行されますが、物理的にすべてのスレッドが同時に実行されるわけではありません。(1つのSMは同時に1つのワープのみをスケジュールし、他のワープは待機します。異なるワープ間の切り替えはゼロオーバーヘッドです。なぜなら、ワープの実行コンテキストはワープのライフサイクル全体でSMによって維持されるからです)

NVIDIA GeForce RTX 3090のCompute Capabilitiesは8.6で、82個のSMを含み、各SMに同時に存在できる最大スレッド数は1536です。同時に並行して実行されるスレッド数はどれくらいですか?並行して実行されるスレッド数はどれくらいですか? 82個のSMを含み、各SMに同時に存在できる最大スレッド数は1536です。つまり、最大で48個のワープが存在できます。ワープはワープスケジューラによって並行して実行され、ワープ内の32本のスレッドは並行して実行されるため、一般的には同時に並行して実行されるスレッド数は82*32=2624であり、並行して実行されるスレッド数は82*32*48=125952です。

3.7 メモリモデル

ストレージ 位置 キャッシュの有無 アクセス権限 生存期間
レジスタ オンチップ デバイス スレッド、カーネル関数と同じ
共有メモリ オンチップ デバイス ブロックと同じ
ローカルメモリ オンボード デバイス スレッド、カーネル関数と同じ
グローバルメモリ オンボード デバイス&ホスト プログラム
テクスチャメモリ、定数メモリ オンボード デバイス&ホスト プログラム

CUDAカーネル関数で定義された変数は、いつレジスタ変数で、いつローカル変数ですか?

以下の3つの状況はローカル変数で、それ以外はレジスタ変数です。

  • コンパイル段階で決定できない配列
  • 配列または構造体が占有するスペースが非常に大きい
  • カーネル関数で定義された変数が多すぎて、レジスタに収まりきらない

レジスタからローカルメモリにオーバーフローしたものは、本質的にグローバルメモリと同じストレージ領域にあります。

image.png image.png

3.8 メモリアクセスモード

グローバルメモリはキャッシュを通じてロード/ストアを実現します。すべてのグローバルメモリへのアクセスはL2キャッシュ(一般的に128バイト)を通じて行われます。

整列アクセス

最初のアドレスはキャッシュ粒度(一般的に32バイト)の偶数倍です(キャッシュラインがデータを取得する開始位置がこのようになっています)

結合アクセス

1つのスレッド束内のすべてのスレッドが連続したメモリブロックにアクセスします。結合アクセスは、スレッド束によるグローバルメモリへの1回のアクセス要求が最小のデータ転送を引き起こすことを意味します(結合度=100%)、それ以外は非結合アクセスです。

5種類のアクセス方法、結合度の計算??

読み取りと書き込みの両方が結合アクセスできない場合、結合書き込みを優先的に保証する必要があります。読み取り専用データの非結合アクセスには、__ldg()関数を使用してキャッシュすることができます。また、共有メモリを使用して結合アクセスに変換することもできます。

3.9 共有メモリとバンクコンフリクト

共有メモリはプログラマーが直接操作できます。 共有メモリは多くのバンクに分割されています。

  • 1つのワープ内のすべてのスレッドが同じバンクの同じアドレスにアクセスする-ブロードキャスト
  • 1つのワープ内の異なるスレッドが1つのバンクの異なるアドレスにアクセスする-バンクコンフリクト
  • 複数のスレッドが同じバンクの同じアドレスにアクセスする-マルチキャスト

Memory Padding メモリパディングでバンクコンフリクトを解決 image.png

パディング操作:sDataの第2次元を+1する、すなわちsData[BS][BS+1] パディングされた部分はデータストレージに使用できず、利用可能な共有メモリの量が減少します。

4 コード

4.1 画像反転CPU

マルチスレッドで画像を反転し、キャッシュを手動で管理します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
void * MTFlipHM(void * tid){
        struct Pixel pix; //temp swap pixel
        int row, col;
        int id = *((int *) tid);
        int start = id * ip.Vpixels/NumThreads;
        int end = start + ip.Vpixels/NumThreads;
        unsigned char buffer[16384];
        for (row = st ; row < ed; row++)
        {
            memcpy(buffer, TheImage[row], ip.Hbytes);
            col = 0;
            while (col < ip.Hpixels * 3 /2){
            pix.B = buffer[col];
            pix.G = buffer[col+1];
            pix.R = buffer[col+2];
            buffer[col]   = buffer[ip.Hpixels*3 - col -3];
            buffer[col+1] = buffer[ip.Hpixels*3 - col -2];
            buffer[col+2] = buffer[ip.Hpixels*3 - col -1];
            buffer[ip.Hpixels*3 - col -3] = pix.B;
            buffer[ip.Hpixels*3 - col -2] = pix.G;
            buffer[ip.Hpixels*3 - col -1] = pix.R;
            col += 3;
            }
        }
        memcpy(TheImage[row],buffer,ip.Hbytes);
        pthread_exit(NULL);
}

void * MTFlipVM(void * tid){
        struct Pixel pix; //temp swap pixel
        int row, col;
        int id = *((int *) tid);
        int start = id * ip.Vpixels/NumThreads;
        int end = start + ip.Vpixels/NumThreads;
        unsigned char buffer1[16384], buffer2[16384];
        for (row = start; row < end; row ++)
        {
            memcpy(buffer1,TheImage[row],ip.Hbytes);
            int  mirrow = ip.Vpixels - 1 - row;
            memcpy(buffer2,TheImage[mirrow],ip.Hbytes);
            // 再错位拷贝即完成交换
            memcpy(TheImage[row],buffer2,ip.Hbytes);
            memcpy(TheImage[mirrow],buffer1,ip.Hbytes);
        }
}

pthread_create(&ThHandle[i], &ThAttr, MTFlipFunc, (void *)&ThParam[i]);
for(i=0; i<NumThreads; i++)
                pthread_join(ThHandle[i], NULL);

4.2 配列の加算

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const int a=1, b=2, c=3;
__global__ void add(double *x, double *y, double* z){
	const int n = blockIdx.x * blockDim.x + threadIdx.x;
	if (n<N)  z[n] = x[n] + y[n];
}
int main(){
	const int N = 1e9;
	const int M = sizeof(double) * N;
	double *h_x = (double*) malloc(M);
	double *h_y = (double*) malloc(M);
	double *h_z = (double*) malloc(M);
	for (int i=0; i<N; i++)
	{
		h_x[i] = a;
		h_y[i] = b;
	}
	double *d_x, *d_y, *d_z;
	cudaMalloc((void**) &d_x, M);
	cudaMalloc((void**) &d_y, M);
	cudaMalloc((void**) &d_z, M);

	const int block_size =  128;
	int grid_size = (N+block_size-1) / block_size;
	add<<<grid_size, block_size>>>(d_x, d_y, d_z);
	cudaMemcpy(h_z, d_z, M, cudaMemcpyDeviceToHost);
	free(h_x),free(h_y),free(h_z);
	cudaFree(d_x),cudaFree(d_y),cudaFree(d_z);
}

4.3 画像反転

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
__global__ void Vflip(uch *ImgDst, uch *ImgSrc, ui Hpixels, ui Vpixels){
ui ThrPerBlk = blockDim.x;
ui MYbid = blockIdx.x;
ui MYtid = threadIdx.x;
ui MYgtid = ThrPerBlk * MYbid + MYtid;
ui BlkPerRow = (Hpixels + ThrPerBlk - 1) / ThrPerBlk; // ceil
ui RowBytes = (Hpixels * 3 + 3) & (~3);
ui MYrow = MYbid / BlkPerRow;
ui MYcol = MYgtid - MYrow*BlkPerRow*ThrPerBlk;
if (MYcol >= Hpixels) return;// col out of range
ui MYmirrorrow = Vpixels - 1 - MYrow;
ui MYsrcOffset = MYrow * RowBytes;
ui MYdstOffset = MYmirrorrow * RowBytes;
ui MYsrcIndex = MYsrcOffset + 3 * MYcol;
ui MYdstIndex = MYdstOffset + 3 * MYcol;
// swap pixels RGB @MYcol , @MYmirrorcol
ImgDst[MYdstIndex] = ImgSrc[MYsrcIndex];
ImgDst[MYdstIndex + 1] = ImgSrc[MYsrcIndex + 1];
ImgDst[MYdstIndex + 2] = ImgSrc[MYsrcIndex + 2];}

__global__ void Hflip(uch *ImgDst, uch *ImgSrc, ui Hpixels){
ui ThrPerBlk = blockDim.x;
ui MYbid = blockIdx.x;
ui MYtid = threadIdx.x;
ui MYgtid = ThrPerBlk * MYbid + MYtid;
ui BlkPerRow = (Hpixels + ThrPerBlk -1 ) / ThrPerBlk; // ceil
ui RowBytes = (Hpixels * 3 + 3) & (~3);
ui MYrow = MYbid / BlkPerRow;
ui MYcol = MYgtid - MYrow*BlkPerRow*ThrPerBlk;
if (MYcol >= Hpixels) return;// col out of range
ui MYmirrorcol = Hpixels - 1 - MYcol;
ui MYoffset = MYrow * RowBytes;
ui MYsrcIndex = MYoffset + 3 * MYcol;
ui MYdstIndex = MYoffset + 3 * MYmirrorcol;
// swap pixels RGB @MYcol , @MYmirrorcol
ImgDst[MYdstIndex] = ImgSrc[MYsrcIndex];
ImgDst[MYdstIndex + 1] = ImgSrc[MYsrcIndex + 1];
ImgDst[MYdstIndex + 2] = ImgSrc[MYsrcIndex + 2];}

4.4 行列転置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
__global__ void transpose(int a[], int b[], int N){
    //分配共享内存
    __shared__ int S[TILE][TILE + 1];
    int bx = blockIdx.x * TILE;
    int by = blockIdx.y * TILE;
    int ix = bx + threadIdx.x;
    int iy = by + threadIdx.y;
    if (ix < N && iy < N)// 读入共享内存
        S[threadIdx.y][threadIdx.x] = a[iy * N + ix];
    __syncthreads();//同步,这是必不可少的
    int ix2 = bx + threadIdx.y;
    int iy2 = by + threadIdx.x;
    if (ix2 <N && iy2 < N)// 写回
        b[ix2 * N + iy2 ] = S[threadIdx.x][threadIdx.y];
}

4.5 正方行列の乗算

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
__shared__ float Mds[WIDTH][TILE_WIDTH];
__shared__ float Nds[TILE_WIDTH][WIDTH];
int bx=blockIdx.x ; int by = blockIdx.y;
int tx=threadIdx.x ; int ty = threadIdx.y;
int Row = by *TILE_WIDTH +ty;
int Col = bx*TILE_WIDTH + tx;
float Pvalue = 0;
for(int m=0; m<WIDTH/TILE_WIDTH; ++m)
    {
    // 每个线程载入M的子矩阵的一个元素
    Mds[ty][tx] = Md[Row*width+(m*TILE_WIDTH+tx)];
    //每个线程载入N的子矩阵的一个元素
    Nds[ty][tx] = Nd[(m*TILE_WIDTH+ty)*width+Col];
	 __syncthreads();
	 for (int k = 0; k < TILE_WIDTH; ++k)
	    Pvalue += Mds[ty][k] * Nds[k][tx];
	 __syncthreads();
	 }
Pd[Row*WIDTH+Col] = Pvalue;//将结果写回P矩阵

4.6 ヒストグラム

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#define SIZE (100*1024*1024)
//通过工具函数big_random_block()来生成随机的字节流
unsigned char *buffer =(unsigned char*)big_random_block( SIZE );
unsigned int histo[256];
for (int i = 0; i<256; i++)
	histo[i] = 0;
for (int i = 0; i < SIZE; i++)
	histo[buffer[i]]++;
long histoCount = 0;
for (int i = 0; i<256; i++) {
	histoCount += histo[i]; }

__global__ void histo_kernel(unsigned char *buffer, long size, unsigned int *histo){
__shared__ unsigned int temp[256];
temp[threadIdx.x] = 0;
__syncthreads();
int i = threadIdx.x + blockIdx.x * blockDim.x;
int offset = blockDim.x *gridDim.x;
while (i<size){
	atomicAdd(&temp[buffer[i]], 1);
	i += offset;
}
__syncthreads();
atomicAdd(&(histo[threadIdx.x]), temp[threadIdx.x]);
}

4.7 リダクション和

リダクション和とTOP Kは類似しており、以下のコードは公式コードです。理解にはこの記事を参照してください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
__global__ void _sum_gpu(int *input, int count, int *output)
{
    __shared__ int sum_per_block[BLOCK_SIZE];

    int temp = 0;
    for (int idx = threadIdx.x + blockDim.x * blockIdx.x;
         idx < count; idx += gridDim.x * blockDim.x
	)
    {// 跨网格循环,一个线程加多个数据,应对海量数据
        temp += input[idx];
    }

    sum_per_block[threadIdx.x] = temp;  //the per-thread partial sum is temp!
    __syncthreads();

    //**********shared memory summation stage***********
    for (int length = BLOCK_SIZE / 2; length >= 1; length /= 2)
    {
        int double_kill = -1;
	if (threadIdx.x < length)
	{
	    double_kill = sum_per_block[threadIdx.x] + sum_per_block[threadIdx.x + length];
	}
	__syncthreads();  //why we need two __syncthreads() here, and,
	
	if (threadIdx.x < length)
	{
	    sum_per_block[threadIdx.x] = double_kill;
	}
	__syncthreads();  //....here ?
	
    } //the per-block partial sum is sum_per_block[0]

    if (blockDim.x * blockIdx.x < count) //in case that our users are naughty
    {
        //the final reduction performed by atomicAdd()
        if (threadIdx.x == 0) atomicAdd(output, sum_per_block[0]);
    }
}

4.8 TOP K

具体的な実装手順は以下の通りです:

  1. データをGPUメモリにコピーします。 float *d_data; cudaMemcpy(d_data, h_data, size, cudaMemcpyHostToDevice);
  2. データを二元組に格納します。
1
2
3
4
5
typedef struct {     float value;     int index; } Tuple;  
Tuple *d_tuples; 
int threadsPerBlock = 256; 
int blocksPerGrid = (n + threadsPerBlock - 1) / threadsPerBlock;
initializeTuples<<<blocksPerGrid, threadsPerBlock>>>(d_data, d_tuples, n);
  1. 二元組に対してリダクション操作を行い、上位K個の最大/最小値のインデックスを取得します。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
int *d_indices;
kReduceKernel<<<blocksPerGrid, threadsPerBlock>>>(d_tuples, d_indices, n, k);
__global__ void kReduceKernel(Tuple *input, int *output, int n, int k) {
    extern __shared__ Tuple shared[];
    int tid = threadIdx.x;
    int i = blockIdx.x * blockDim.x + threadIdx.x;
    shared[tid] = (i < n) ? input[i] : Tuple{0, 0};
    __syncthreads();
    for (int s = blockDim.x / 2; s > 0; s >>= 1) {
        if (tid < s)
            shared[tid] = (shared[tid].value > shared[tid + s].value) ? shared[tid] : shared[tid + s];
        __syncthreads();
    }

    if (tid == 0)
        output[blockIdx.x] = shared[0].index;
}
  1. CPUで元のデータを復元し、インデックスに基づいてソートし、上位K個の最大/最小値を取得します。
1
2
3
4
5
cudaMemcpy(h_indices, d_indices, size, cudaMemcpyDeviceToHost);  
for (int i = 0; i < k; ++i) {     
    int index = h_indices[i];     
    h_result[i] = h_data[index]; }  
std::sort(h_result, h_result + k);

5 実験

実験一:PIの3つの求め方、スレッドプール

実験二:行列の乗算、転置、リダクション、TOP K 問題

グローバルメモリ、共有メモリの最適化、バンクコンフリクトの最適化

手書き並列計算レポート3-4ページ CPU PIの3つの求め方 プロデューサーコンシューマーモデル GPU メインプログラムフローを一度明確にするだけで、カーネル関数は異なり、重点はグローバルメモリと共有メモリの実装、行列の乗算ヒストグラム(グリッドをまたぐループ)リダクション100万配列の最大値 レポートは試験時に提出

6 20級過去問

6.1 簡単な質問

アムダールの法則、プロセッサn個、直列40%、加速比の限界を求める。

RGB画像680*480を4つのスレッドに分割(どのように分割するかは言及されていない)、各スレッドが処理するピクセル範囲とバイト範囲;

PPTスレッド束並行並列数の例題原題;

NVIDIA GeForce RTX 3090のCompute Capabilitiesは8.6で、82個のSMを含み、各SMに同時に存在できる最大スレッド数は1536です。同時に並行して実行されるスレッド数はどれくらいですか?並行して実行されるスレッド数はどれくらいですか? 82個のSMを含み、各SMに同時に存在できる最大スレッド数は1536です。つまり、最大で48個のワープが存在できます。ワープはワープスケジューラによって並行して実行され、ワープ内の32本のスレッドは並行して実行されるため、一般的には同時に並行して実行されるスレッド数は82*32=2624であり、並行して実行されるスレッド数は82*32*48=125952です。

行列転置プロセスのある要素のグローバルID;

image.png

要素3のグローバルIDを問う

データをコピーしながら転置することはできますか(CUDAストリーム);

6.2 プログラム分析問題

第一問、授業練習原題;<4,4>が<5,5>に変更された;プロセスを説明する必要がある;

プログラム分析一

第二問、アトミック操作のないヒストグラムリダクション、問題点は何か;

6.3 CPUプログラミング

配列a[2,1000000]の素数を求め、10個のスレッドで等分する;

スレッドプール擬似コード:クライアント、サーバー(メール機能、エクスポート機能、トラフィック統計などの一連の機能);

6.4 GPUプログラミング

グローバルメモリの行列乗算;

ベクトルa,bの内積、次元=1024000000;blockdim.x = blockdim.y =16が固定されている;グリッドを設計する;共有メモリを使用して最適化し、バンクコンフリクトを解決し、結果をCPUに戻して最終的に結合することを要求する。

感想:非コード問題は赤ちゃんバス、コード問題は強烈なパンチ、全く書き終わらない。

Buy me a coffee~
Tim 支付宝支付宝
Tim 贝宝贝宝
Tim 微信微信
0%