cuda学习笔记5

本节记录常量内存的用法,以及如何用时间函数测量CUDA应用的性能。

概述

常量内存用于保存在核函数执行期间不会发生变化的数据,由于GPU的性能瓶颈通常不在于芯片的数学吞吐能力,而在于芯片的内存带宽,合理利用常量内存能有效减小内存的带宽的消耗。

如何使用常量内存

  1. 声明常量内存的方法: 在声明的变量前加 constant 修饰符,如下:
1
__constant__ Sphere s[num]

此时不需要再用 cudaMalloc() 或者 cudaFree() 来申请或释放内存空间,编译器会自动为这个数组提交一个固定的大小。

  1. 将主机内存复制到设备内存的函数

cudaMemcpy() 会将主机内存复制到全局内存,而 cudaMemcpyToSymbol() 会将主机内存复制到常量内存。

常量内存为什么有效

  • 对常量内存的单次操作可以广播到其他临近线程,范围为半个线程束(Wrap)。
  • 常量内存的数据将缓存起来,因此对相同地址的连续读操作不会产生额外的内存通信量。

在CUDA架构中,线程束是指包含32个线程的集合,这个线程集合被“编织”在一起并且以“步调一致”的形式执行,在程序的每一行,线程束中的每个线程都在不同的数据中执行相同的操作。

当这半个线程束读取常量内存相同地址时,才可以大幅度提升性能,否则,这半个线程束的请求会被串行化,在这个情况下性能反而会降低。

使用事件来测量性能

要测量在核函数中执行的时间(包括核函数和设备内存的赋值操作),可以这样写:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
cudaEvent_t     start, stop;
cudaEventCreate(&start);
cudaEventCreate(&stop);
cudaEventRecord(start, 0);
*****************
GPU上执行一些工作(包括前后的设备内存复制)
*****************
cudaEventRecord(stop, 0);
cudaEventSynchronize(stop);
float   elapsedTime;
cudaEventElapsedTime(&elapsedTime,start, stop);
printf("Time to generate:  %3.1f ms\n", elapsedTime);

cudaEventDestroy(start);
cudaEventDestroy(stop);

不可以用该事件函数对包含主机函数和设备函数的混合代码一起计时!

简单的光线追踪

下面的代码时利用常量内存实现的一个简单的光线追踪器。

  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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#include "cuda.h"
#include "book.h"
#include "image.h"

#define DIM 1024

#define rnd( x ) (x * rand() / RAND_MAX)
#define INF     2e10f

struct Sphere {
	float   r, b, g;
	float   radius;
	float   x, y, z;
	__device__ float hit(float ox, float oy, float *n) {
		float dx = ox - x;
		float dy = oy - y;
		if (dx*dx + dy*dy < radius*radius) {
			float dz = sqrtf(radius*radius - dx*dx - dy*dy); //光线投射进球的深度
			*n = dz / sqrtf(radius * radius); //归一化
			return dz + z;
		}
		return -INF;
	}
};
#define SPHERES 20

__constant__ Sphere s[SPHERES];

__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;
	float   ox = (x - DIM / 2);
	float   oy = (y - DIM / 2);

	float   r = 0, g = 0, b = 0;
	float   maxz = -INF;
	for (int i = 0; i<SPHERES; i++) {
		float   n;
		float   t = s[i].hit(ox, oy, &n);
		if (t > maxz) {
			float fscale = n;
			r = s[i].r * fscale;
			g = s[i].g * fscale;
			b = s[i].b * fscale;
			maxz = t;
		}
	}

	ptr[offset * 4 + 0] = (int)(r * 255);
	ptr[offset * 4 + 1] = (int)(g * 255);
	ptr[offset * 4 + 2] = (int)(b * 255);
	ptr[offset * 4 + 3] = 255;
}

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

int main(void) {
	DataBlock   data;
	// capture the start time
	cudaEvent_t     start, stop;
	HANDLE_ERROR(cudaEventCreate(&start));
	HANDLE_ERROR(cudaEventCreate(&stop));
	HANDLE_ERROR(cudaEventRecord(start, 0));

	IMAGE bitmap(DIM, DIM);
	unsigned char   *dev_bitmap;

	// allocate memory on the GPU for the output bitmap
	HANDLE_ERROR(cudaMalloc((void**)&dev_bitmap,
		bitmap.image_size()));

	// allocate temp memory, initialize it, copy to constant
	// memory on the GPU, then free our temp memory
	Sphere *temp_s = (Sphere*)malloc(sizeof(Sphere) * SPHERES);
	for (int i = 0; i<SPHERES; i++) {
		temp_s[i].r = rnd(1.0f);
		temp_s[i].g = rnd(1.0f);
		temp_s[i].b = rnd(1.0f);
		temp_s[i].x = rnd(1000.0f) - 500;
		temp_s[i].y = rnd(1000.0f) - 500;
		temp_s[i].z = rnd(1000.0f) - 500;
		temp_s[i].radius = rnd(100.0f) + 20;
	}
	HANDLE_ERROR(cudaMemcpyToSymbol(s, temp_s,
		sizeof(Sphere) * SPHERES));
	free(temp_s);

	// generate a bitmap from our sphere data
	dim3    grids(DIM / 16, DIM / 16);
	dim3    threads(16, 16);
	kernel << <grids, threads >> >(dev_bitmap);

	// copy our bitmap back from the GPU for display
	HANDLE_ERROR(cudaMemcpy(bitmap.get_ptr(), dev_bitmap,
		bitmap.image_size(),
		cudaMemcpyDeviceToHost));

	// get stop time, and display the timing results
	HANDLE_ERROR(cudaEventRecord(stop, 0));
	HANDLE_ERROR(cudaEventSynchronize(stop));
	float   elapsedTime;
	HANDLE_ERROR(cudaEventElapsedTime(&elapsedTime,
		start, stop));
	printf("Time to generate:  %3.1f ms\n", elapsedTime);

	HANDLE_ERROR(cudaEventDestroy(start));
	HANDLE_ERROR(cudaEventDestroy(stop));

	HANDLE_ERROR(cudaFree(dev_bitmap));

	// display
	bitmap.show_image();
}

效果如文章的开头。

updatedupdated2019-12-282019-12-28