参考教程:
1. QINZHAOYU/CudaSteps
2. cuda编程(一)基础
3. CUDA C/C++ 教程一:加速应用程序
1. 运行CUDA api时候添加如下宏:
#define CHECK(call) \
do { \
const cudaError_t error_code = call; \
if (error_code != cudaSuccess) \
{ \
printf("CUDA ERROR: \n"); \
printf(" FILE: %s\n", __FILE__); \
printf(" LINE: %d\n", __LINE__); \
printf(" ERROR CODE: %d\n", error_code); \
printf(" ERROR TEXT: %s\n", cudaGetErrorString(error_code)); \
exit(1); \
} \
}while(0);
2. 核函数检查
因为核函数没有返回值
,所以无法直接检查核函数错误。间接的方法是,在调用核函数后执行:
CHECK(cudaGetLastError()); // 捕捉同步前的最后一个错误。
CHECK(cudaDeviceSynchronize()); // 同步主机和设备。
3. 设置环境变量 CUDA_LAUNCH_BLOCKING为1
这样所有核函数的调用都将不再是异步的,而是同步的。主机调用一个核函数之后必须等待其执行完,才能向下执行。
一般仅用于程序调试。
export CUDA_LAUNCH_BLOCKING=1
4. CUDA-MEMCHECK 检查内存错误
工具集
:memcheck, racecheck, initcheck, synccheck
cuda-memcheck ./bin/check.exe
详情参考:CUDA-MEMCHECK
cudaEvent_t start, stop;
CHECK(cudaEventCreate(&start)); // 创建cuda 事件对象。
CHECK(cudaEventCreate(&stop));
CHECK(cudaEventRecord(start)); // 记录代表开始的事件。
cudaEventQuery(start); // 强制刷新 cuda 执行流。
// run code.
CHECK(cudaEventRecord(stop));
CHECK(cudaEventSynchronize(stop)); // 强制同步,让主机等待cuda事件执行完毕。
float elapsed_time = 0;
CHECK(cudaEventElapsedTime(&curr_time, start, stop)); // 计算 start 和stop间的时间差(ms)。
printf("host memory malloc and copy: %f ms.\n", curr_time - elapsed_time);
C++优化等级-O3
,指定GPU计算能力-arch=sm_50
,双精度版本-DUSE_DP
(耗时长)。nvcc -O3 -arch=sm_50 -DUSE_DP -o ./bin/clock.exe add.cu clock.cu main.cpp
nvprof ./bin/clock
CUDA 中的内存类型有:全局内存、常量内存、纹理内存、寄存器、局部内存、共享内存
。
核函数中所有线程
都可以访问的内存,可读可写,由主机端分配和释放,如cudaMalloc() 的设备内存特点
:静态全局内存变量
__device__ real epsilon; // 单个静态全局内存变量, `__device` 表示是设备中的变量。
__device__ real arr[10]; // 固定长度的静态全局内存数组变量。
访问权限:
cudaMemcpyToSymbol() 和 cudaMemcpyFromSymbol()
调用。2.常量内存
3.纹理内存
(texture memory),类似常量内存。
将某些只读的全局内存数据用 __ldg()
函数通过只读数据缓存(read-only data cache)读取,
既可以达到使用纹理内存的加速效果,
全局内存的读取在默认情况下就利用了 __ldg() 函数,所以不需要显式地使用
。
4.寄存器
(register)
;5.共享内存
位于芯片上
,读写速度较快
。整个线程块可见
,一个线程块上的所有线程都可以访问共享内存上的数据;共享内存的生命周期也与所属线程块一致。6.L1 和 L2 缓存
对全局内存的访问将触发内存事务,即数据传输。
一次 数据传输处理 的数据量在默认情况下是 32 字节
。 一次数据传输中,从全局内存转移到 L2 缓存的一片内存的首地址一定是 32 的整数倍。
也就是说,一次数据传输只能从全局内存读取地址为 0-31 字节、32-63 字节等片段的数据。
合并度
:如果所有数据传输处理的数据都是线程束所需要的,则合并度为 100%,即 合并访问;
否则,即为 非合并访问。
矩阵转置
作用:
高效
的线程块内部的通信;合并度
。线程块对数据分片归约
,最后再一并求和。__syncthreads()
:实现一个线程块中所有线程按照代码出现的顺序执行指令,但是不同线程块之间依然是独立、异步的。__shared__
定义一个共享内存变量,cuda 中,一个线程的原子操作
可以在不受其他线程的任何操作的影响下
完成对某个(全局内存或共享内存)数据的一套“读-改-写”
操作。
9.1 完全在 GPU 中进行归约
什么是归约
?
[参考]:Cuda归约运算
归约(redution
)是一类并行算法,对传入的O(N)个输入数据,使用一个二元的复合结合律的操作符,生成O(1)的结果。这类操作包括取最小、最大、求和、平方求和、逻辑与、逻辑或、向量点积。归约也是其他高级运算中要用的基础算法。
有两种方法能够在GPU中得到最终结果
9.2 原子函数
原子函数对其第一个参数指向的数据进行一次“读-写-改”
的原子操作,是不可分割的操作。第一个参数可以指向全局内存
,也可以指向共享内存
。
原子函数的返回值为所指地址的旧值
。
常见原子函数: