参考书目: GPU高性能编程CUDA实战
书目网页链接: https://hpc.pku.edu.cn/docs/20170829223652566150.pdf
该博客参考于上述书籍,虽然书有一点老,但是作为初学者而言仍然能学到很多东西。
本书所包含的代码都在下面的连接中,可以下载来学习: https://developer.nvidia.com/cuda-example
CUDA C简介
首先来看一个CUDA C的示例:
#include "../common/book.h"
int main(){
prinf("Hello World!\n");
return 0;
}
这个示例只是为了说明,CUDA C与熟悉的标准C在很大程度上是没有区别的。
核函数调用
在GPU设备上执行的函数通常称为核函数(Kernel)
#include <iostream>
__global__ void kernel(){
}
int main(){
kernel<<<1, 1>>>();
printf("Hello World!\n");
return 0;
}
跟之前的代码相比多了两处
- 一个空的函数kernel(),并且带有修饰符__global__。
- 对这个空函数的调用,并且带有修饰字符«<1, 1»>。
这个__global__可以认为是告诉编译器,函数应该编译为在设备而不是在主机上运行。函数kernel()将被交给编译器设备代码的编译器,而main()函数将被交给主机编译器。
传递参数
以下是对上述代码的进一步修改,可以实现将参数传递给核函数
#include <iostream>
#include "book.h"
__global__ void add(int a, int b, int* c){
*c = a + b;
printf("c is %d\n", *c);
}
int main(void){
int c = 0;
int* dev_c;
printf("original c is %d\n", c);
HANDLE_ERROR(cudaMalloc((void**)&dev_c, sizeof(int)));
add<<<1, 1>>>(2, 7, dev_c);
HANDLE_ERROR(cudaMemcpy(&c, dev_c, sizeof(int), cudaMemcpyDeviceToHost));
cudaFree(dev_c);
printf("2 + 7 = %d\n", c);
return 0;
}
其中"book.h"包含了HANDLE_ERROR,也可以不使用"book.h"而是在代码中添加HANDLE_ERROR函数。
#include <iostream>
static void HandleError( cudaError_t err,
const char *file,
int line ) {
if (err != cudaSuccess) {
printf( "%s in %s at line %d\n", cudaGetErrorString( err ),
file, line );
exit( EXIT_FAILURE );
}
}
#define HANDLE_ERROR( err ) (HandleError( err, __FILE__, __LINE__ ))
__global__ void add(int a, int b, int* c){
*c = a + b;
printf("c is %d\n", *c);
}
int main(void){
int c = 0;
int* dev_c;
printf("original c is %d\n", c);
HANDLE_ERROR(cudaMalloc((void**)&dev_c, sizeof(int)));
add<<<1, 1>>>(2, 7, dev_c);
HANDLE_ERROR(cudaMemcpy(&c, dev_c, sizeof(int), cudaMemcpyDeviceToHost));
printf("2 + 7 = %d\n", c);
cudaFree(dev_c);
return 0;
}
- cudaMalloc()用来分配内存,这个函数的调用行为非常类似于标准C函数的malloc(),但该函数作用是告诉CUDA运行时在设备上分配内存。
- 第一个参数是一个指针,指向用于保存新分配内存地址的变量。第二个参数是分配内存的大小。
- 该函数返回的类型是void*。
- 不能使用标准C的free()函数来释放cudaMallocc()分配的内存。要释放cudaMalloc()分配的内存,需要调用cudaFree()。
- HANDLE_ERROR()是定义的一个宏,作为辅助代码的一部分,用来判断函数调用是否返回了一个错误值,如果是的话,将输出相应的错误消息。
- 在主机代码中可以通过调用cudaMemcpy()来访问设备上的内存。
- 第一个参数是目标(target)指针,第二个参数是源(source)指针,第三个参数分配内存大小。第四个参数则是指定设备内存指针。
- 第四个参数一般有cudaMemcpyDeviceToHost,cudaMemcpyHostToDevice, cudaMemcpyDeviceToDevice三种。cudaMemcpyDeviceToHost说明我们将设备内存指针的数据传递给主机内存指针,此时第一个参数指针是在主机上,第二个参数指针是在设备上。cudaMemcpyHostToDevice说明我们将主机内存指针的数据传递给设备内存指针,此时第一个参数指针是在设备上,第二个参数指针是在主机上。此外还可以通过传递参数cudaMemcpyDeviceToDevice莱高速运行时这两个指针都在设备上。如果源指针和目标指针都在主机上,则可以直接调用memcpy()函数。
查询设备
我们可以使用cudaGetDeviceCount()来查询设备数量(比如GPU数量)。
int count;
HANDLE_ERROR(cudaGetDeviceCount(&count));
CUDA设备属性包含很多信息,可以在书上或者NVIDIA官方网站上查到。
Comments