cuda学习笔记4

本文主要介绍如何用GPU共享内存,线程块和线程的二维索引,线程同步的概念,并利用这些概念实现生成位图。

本文概要

  • 线程块和线程的二维索引表示

  • GPU上进行点积运算

  • 生成位图

线程块和线程的二维索引表示

同样地,和前文一维索引的调用方法相比,只需修改核函数的索引计算方法和核函数的调用方式即可。

  • 核函数的索引计算方法: 将:
1
int tid = threadIdx.x + blockIdx.x * blockDim.x;

改为:

1
2
3
int x = threadIdx.x + blockIdx.x * blockDim.x;
int y = threadIdx.y + blockIdx.y * blockDim.y;
int tid = x + y * blockDim.x * gridDim.x;

这样每一个 tid 索引 在二维表示中将指向唯一的线程,二维表示在处理图像上是常用的。

  • 核函数的调用方式: 将:
1
func_kernel << <(N + 127) / 128, 128 >>(参数)

改为:

1
2
3
dim3    blocks(DIM/16,DIM/16); ////二维线程块
dim3    threads(16,16); ////二维线程
func_kernel<<<blocks,threads>>>(参数);

GPU上进行点积运算

将线程块分解为线程的目的,除了物理设备上线程块最大数目的限制,还有一个原因是 CUDA C支持共享内存。对于GPU上的每一个线程块,编译器都为该共享变量创建一个副本,而线程块中的每一个线程共享这块内存。由于共享内存驻留在物理GPU上而不是GPU之外的系统内存中,访问共享内存的延迟要远低于访问普通内缓存区的延迟。

下面的例子是在GPU上实现点积运算,GPU中 每个线程块都完成以下的操作:

  1. 首先要确定每个线程的起始位置和每次计算的偏移量,将计算的结果累加到该线程块中该线程对应的共享内存区域里。
  2. 每个线程完成计算后,执行同步操作,确保每个所有线程都已经执行完前面的操作。
  3. 规约,将每个线程块的共享内存区域的值相加,送给GPU的全局变量。

代码如下:

  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
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
#include "../common/book.h"

#define imin(a,b) (a<b?a:b)

const int N = 33 * 1024;
const int threadsPerBlock = 256;
const int blocksPerGrid =
imin(32, (N + threadsPerBlock - 1) / threadsPerBlock);


__global__ void dot(float *a, float *b, float *c) {
	__shared__ float cache[threadsPerBlock];  ////共享内存缓存区(驻留在物理GPU上),编译器为每一个线程块生成一个共享变量的副本。同一线程块中的每个线程共享这块内存。
	int tid = threadIdx.x + blockIdx.x * blockDim.x; ////索引偏置
	int cacheIndex = threadIdx.x; ////由于每一个线程块都具有一个共享内存的副本,故共享内存的索引就是该线程块上的线程索引。

	float   temp = 0;
	while (tid < N) {
		temp += a[tid] * b[tid];
		tid += blockDim.x * gridDim.x;
	}

	// set the cache values
	cache[cacheIndex] = temp; ////每个线程处理的数据,相加放在对应的共享内存区域中。

	// synchronize threads in this block
	__syncthreads();  ////线程同步,当所有的线程都执行完以上操作时,才能继续执行下一步。

	// for reductions, threadsPerBlock must be a power of 2
	// because of the following code
	////归约,将共享内存区域每一个储存的值相加起来,由于规约每次迭代数量减半,要求 threadsPerBlock 是2 的指数倍。
	int i = blockDim.x / 2;
	while (i != 0) {
		if (cacheIndex < i)
			cache[cacheIndex] += cache[cacheIndex + i];
		__syncthreads(); ////线程同步。同样地,执行下一次规约迭代前,必须确保所有线程都已经执行完上面相加的操作。
		i /= 2;
	}

	if (cacheIndex == 0)
		c[blockIdx.x] = cache[0];  ////把一个线程块中的最后计算得到的相加值返还给全局变量。
}


int main(void) {
	float   *a, *b, c, *partial_c;
	float   *dev_a, *dev_b, *dev_partial_c;

	// allocate memory on the cpu side
	a = (float*)malloc(N * sizeof(float));
	b = (float*)malloc(N * sizeof(float));
	partial_c = (float*)malloc(blocksPerGrid * sizeof(float)); ////该变量的内存大小是线程块的数目* float内存大小。因为在GPU中,每个线程块最后返还一个相加值。

	// allocate the memory on the GPU
	HANDLE_ERROR(cudaMalloc((void**)&dev_a,
		N * sizeof(float)));
	HANDLE_ERROR(cudaMalloc((void**)&dev_b,
		N * sizeof(float)));
	HANDLE_ERROR(cudaMalloc((void**)&dev_partial_c,
		blocksPerGrid * sizeof(float)));

	// fill in the host memory with data
	for (int i = 0; i<N; i++) {
		a[i] = i;
		b[i] = i * 2;
	}

	// copy the arrays 'a' and 'b' to the GPU
	HANDLE_ERROR(cudaMemcpy(dev_a, a, N * sizeof(float),
		cudaMemcpyHostToDevice));
	HANDLE_ERROR(cudaMemcpy(dev_b, b, N * sizeof(float),
		cudaMemcpyHostToDevice));

	dot << <blocksPerGrid, threadsPerBlock >> >(dev_a, dev_b,
		dev_partial_c);

	// copy the array 'c' back from the GPU to the CPU
	HANDLE_ERROR(cudaMemcpy(partial_c, dev_partial_c,
		blocksPerGrid * sizeof(float),
		cudaMemcpyDeviceToHost));

	// finish up on the CPU side
	c = 0;
	for (int i = 0; i<blocksPerGrid; i++) {
		c += partial_c[i];
	}
////cpu求点积
#define sum_squares(x)  (x*(x+1)*(2*x+1)/6)
	printf("Does GPU value %.6g = %.6g?\n", c,
		2 * sum_squares((float)(N - 1)));

	// free memory on the gpu side
	HANDLE_ERROR(cudaFree(dev_a));
	HANDLE_ERROR(cudaFree(dev_b));
	HANDLE_ERROR(cudaFree(dev_partial_c));

	// free memory on the cpu side
	free(a);
	free(b);
	free(partial_c);
}

值得注意的一点是,上面代码的线程同步 __syncthreads() 中,需确保每一个线程都执行该操作,否则任何线程都不能执行 __syncthreads() 后面的操作。比如:

将:

1
2
3
4
5
6
while (i != 0) {
		if (cacheIndex < i)
			cache[cacheIndex] += cache[cacheIndex + i];
		__syncthreads(); 
		i /= 2;
	}

改为:

while (i != 0) {
		if (cacheIndex < i)
		{
			cache[cacheIndex] += cache[cacheIndex + i];
		    __syncthreads();
		}
		i /= 2;
	}

后,代码将运行错误,因为不是所有线程都满足 “cacheIndex < i” 该条件。

生成位图

最后是一个利用线程块和线程的二维索引生成位图的例子:涉及共享内存,线程同步,二位索引的利用。

 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
50
51
52
53
54
55
56
57
58
#include "cuda.h"
#include "../common/book.h"
#include "../common/image.h"

#define DIM 1024
#define PI 3.1415926535897932f

__global__ void kernel( unsigned char *ptr ) {
    // map from threadIdx/BlockIdx to pixel position
    int x = threadIdx.x + blockIdx.x * blockDim.x;
    int y = threadIdx.y + blockIdx.y * blockDim.y;
    int offset = x + y * blockDim.x * gridDim.x;

    __shared__ float    shared[16][16]; ////二维共享内存数组

    // now calculate the value at that position
    const float period = 128.0f;

    shared[threadIdx.x][threadIdx.y] =
            255 * (sinf(x*2.0f*PI/ period) + 1.0f) *
                  (sinf(y*2.0f*PI/ period) + 1.0f) / 4.0f;

    // removing this syncthreads shows graphically what happens
    // when it doesn't exist.  this is an example of why we need it.
    __syncthreads(); ////同步

    ptr[offset*4 + 0] = 0;
    ptr[offset*4 + 1] = shared[15-threadIdx.x][15-threadIdx.y];
    ptr[offset*4 + 2] = 0;
    ptr[offset*4 + 3] = 255;
}

// globals needed by the update routine
struct DataBlock {
    unsigned char   *dev_bitmap;
};

int main( void ) {
    DataBlock   data;
    IMAGE bitmap( DIM, DIM );
    unsigned char    *dev_bitmap;

    HANDLE_ERROR( cudaMalloc( (void**)&dev_bitmap,
                              bitmap.image_size() ) );
    data.dev_bitmap = dev_bitmap;

    dim3    grids(DIM/16,DIM/16);
    dim3    threads(16,16);
    kernel<<<grids,threads>>>( dev_bitmap );

    HANDLE_ERROR( cudaMemcpy( bitmap.get_ptr(), dev_bitmap,
                              bitmap.image_size(),
                              cudaMemcpyDeviceToHost ) );
                              
    HANDLE_ERROR( cudaFree( dev_bitmap ) );
                              
    bitmap.show_image();
}
updatedupdated2019-12-282019-12-28