转载于CUDA编程的错误处理
无论是基于CPU的编程还是基于GPU的编程,当我们调用了一个API运行程序产生了错误,就会导致程序运行结果有悖于理论结果,甚至导致程序崩溃。因此,错误的检测和错误的处理在编程中是极为重要的。当我们能够定位错误的原因,错误的纠正才能更快更准确。
我们来回顾一下在Linux系统编程中的错误处理(基于CPU)。
在Linux系统编程中,错误是通过函数的返回值和特殊变量 errno 描述的。如一些函数调用的返回值只是告知你该函数调用发生了错误,但是并没有告诉你是什么错误。其实,在调用该系统函数发生了错误后,全局变量errno的值将被改写,改写后的值和特定的错误相对应。如当errno的值被改写为EACCES=1时,代表“权限不足”,类似的错误映射还有很多。
在头文件 errno.h 中,定义了errno变量:
extern int errno;
那么,我们如何将errno的值所代表的错误信息告知自己呢?
方法一:
#include
void perror (const char *str);
//该函数会向stderr打印以str指向的字符串为前缀,后紧跟一个冒号,最后是errno表示的错误信息。举个栗子:
FILE *fd;
fd = fopen("file.txt","r");
if (NULL == fd) {
perror("fopen");
exit(0);
}
当无法找到文件“file.txt”时,系统会在屏幕上打印这样的字样(你可以查看下此时的errno,errno = ENOENT)
fopen: No such file or directory
方法二:
#include
char * strerror (int errnum);
int strerror_t (int errnum, char *buf, size_t len);
//这两个函数就是将errno作为输入实参,来将错误信息以字符串的形式返回或者写入buf里面。举个例子
FILE *fd;
fd = fopen("file.txt","r");
if (NULL == fd) {
fprintf(stderr, "fopen : %s\n", strerror(errno));
exit(0);
}
当无法找到文件“file.txt”时,系统会在屏幕上打印这样的字样
fopen: No such file or directory
errno使用注意事项:
当跨函数使用errno时,注意保存errno的值到临时变量里面,以防被更改。
在多线程编程中,每个线程都维护一个errno,因此它是线程安全的
和errno变量类似,在CUDA编程中,也定义了错误类型 cudaError_t ,只不过这个错误值一般是runtime API的返回值。当且仅当这个API返回值为cudaSuccess时,才说明这个API调用正确。
typedef enumcudaError cudaError_t;
关于cudaError_t类型变量的值很多,有70+种,具体的大家可以去查看nvidia的CUDA Runtime API手册。
那么问题来了,当发生了API调用错误,我们如何知道错误信息呢?
主要通过如下两个函数:
__host__ __device__ cudaError_t cudaGetLastError ( void )
__host__ __device__ cudaError_t cudaPeekAtLastError ( void )
这两个函数获取最后的错误信息,返回值是一个cudaError_t类型。不同的是,cudaGetLastError()函数将重新将系统的全局错误信息变量重置为cudaSuccess,而cudaPeekAtLastError() 函数不会有这样的操作。
注意事项:
任何CUDA API调用都会可能出错。因此,假如你要定位是否是API 1调用错误,要保证这个API之前的所有API调用都没有错误,然后在API 1调用后面加上cudaGetLastError();
Kernel的启动是异步的,为了定位它是否出错,记得加上cudaDeviceSynchronize()函数进行同步,然后再调用cudaGetLastError();
Ok, 同样的,我们得到了错误类型的值,如果知道它代表的错误信息含义呢?去参考手册上查找错误信息映射就太麻烦了,CUDA提供了如下的两个API来显示对应的错误信息:
__host__ __device__ const char* cudaGetErrorName ( cudaError_t error )
__host__ __device__ const char* cudaGetErrorString ( cudaError_t error )
这两个函数的形参输入就是cudaError_t错误类型变量,返回了错误名称和错误信息。举个栗子:
cudaError_t err;
err = cudaMemcpy(p_d, p_h, sizeof(float)*1024, cudaMemcpyHostToDevice);
if (err != cudaSuccess) {
fprintf(stderr, "cudaMemcpy : %s\n", cudaGetErrorString(cudaGetLastError()));
exit(EXIT_FAILURE);
}
小技巧:
在CUDA examples中我们发现,程序中会有checkCudaErrors( … ),这个是定义了一个宏,来确保函数API调用正确,这种方式会使编程更加的简介明了。我们也可以自己定义一个宏:
#define checkCudaErrors( a ) do { \
if (cudaSuccess != (a)) { \
fprintf(stderr, "Cuda runtime error in line %d of file %s \
: %s \n", __LINE__, __FILE__, cudaGetErrorString(cudaGetLastError()) ); \
exit(EXIT_FAILURE); \
} \
} while(0);
因此,这里,我们就使用这个宏来分析runtime api是否调用正确了:
checkCudaErrors( cudaMemcpy(p_d, p_h, sizeof(float)*1024, cudaMemcpyHostToDevice) );
另外从另一本书上看的处理错误定义的宏
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__ ))
#define HANDLE_NULL( a ) {if (a == NULL) { \
printf( "Host memory failed in %s at line %d\n", \
__FILE__, __LINE__ ); \
exit( EXIT_FAILURE );}}