我正在写一个"错误检查"遇到错误时,函数with抛出std::runtime_error
(下面的代码示例)。
的情况:
让我们在foo
构造函数中(在下面的文件:fooClass.h
示例中)说我能够成功设置cuda设备并分配gpu内存,但我对cudaMemcpy(***)
的调用已经返回出于任何原因的错误。这意味着我的errchk(***)
函数抛出异常并将控制转移到main()
函数(在文件中:Start.cpp
)。
因为foo
构造函数没有完成它的工作,所以没有创建foo
对象而且没有调用~foo()
析构函数,因此之前分配的gpu资源没有被释放。
我知道try
中的main()
块应该以相反的顺序将所有资源解除分配给它们的初始化(每当抛出异常时)。
问题#1:
这是否意味着我遇到了gpu内存泄漏?或者以某种方式throw
,try
和catch
可以处理这种情况?
问题#2(?基于意见?):
我正在学习C ++异常系统,因此我想知道在所有地方抛出异常是否是一个好主意,只能在main()
函数中捕获它们?
我认为这是值得考虑的,因为那样我就可以在"自然"方式 - return EXIT_FAILURE;
反对exit(EXIT_FAILURE);
ErrorCheck.h :(整个文件)
#pragma once
#ifndef __ERROR_CHECK_H__
#define __ERROR_CHECK_H__
#include <stdexcept>
// The function:
template <typename T>
bool errchk(const T check, const char* file, unsigned int line, const char* from, const char* func);
// How To call it:
#define ERRCHK(_check) \
errchk(_check, __FILE__, __LINE__, __FUNC__, #_check)
#endif // !__ERROR_CHECK_H__
ErrorCheck.cpp :(简化版)
// Include:
#include <cuda.h> // cudaError_t
#include <cuda_runtime_api.h>
#include <cufft.h> // cufftResult_t
#include <cublas.h> // cublasStatus_t
#include <curand_kernel.h> // curandStatus_t
#include <cusolver_common.h> // cusolverStatus_t
#include <cusparse.h> // cusparseStatus_t
#include <stdexcept>
#include "ErrorCheck.h"
// Functions bellow are overloaded 7 times for every error type from headers included above
inline const bool testForError(const Type & check) { return check != SuccessValue; };
inline const char * getErrorName(const Type & error) { /* ... */ };
inline const char * getErrorString(const Type & error) { /* ... */ };
// The function:
template <typename T, T successValue>
void errchk(const T check, const char* file, unsigned int line, const char* from, const char* func)
{
if (testForError(check)) {
// generate error description in form of a string.
throw std::runtime_error(errorDescription);
}
}
// Instantiations:
template void errchk <bool > (const bool check, const char * file, unsigned int line, const char * from, const char * func);
template void errchk <cudaError_t > (const cudaError_t check, const char * file, unsigned int line, const char * from, const char * func);
template void errchk <cufftResult_t > (const cufftResult_t check, const char * file, unsigned int line, const char * from, const char * func);
template void errchk <cublasStatus_t > (const cublasStatus_t check, const char * file, unsigned int line, const char * from, const char * func);
template void errchk <curandStatus_t > (const curandStatus_t check, const char * file, unsigned int line, const char * from, const char * func);
template void errchk <cusolverStatus_t> (const cusolverStatus_t check, const char * file, unsigned int line, const char * from, const char * func);
template void errchk <cusparseStatus_t> (const cusparseStatus_t check, const char * file, unsigned int line, const char * from, const char * func);
fooClass.h:
#include "ErrorCheck.h"
class foo
{
private:
float * dev_floatArray;
float * host_floatArray;
public:
foo() {
// Do something...
ERRCHK(cudaSetDevice(0));
ERRCHK(cudaMalloc(&dev_floatArray, 10000 * sizeof(float)));
// Do something...
ERRCHK(cudaMemcpy(host_floatArray, dev_floatArray, 10000 * sizeof(float), cudaMemcpyDeviceToHost));
// Do something...
}
~foo() {
// Do something...
ERRCHK(cudaFree(dev_floatArray));
ERRCHK(cudaDeviceReset());
}
}
Start.cpp:
#include <iostream>
#include "fooClass.h"
using namespace std;
int main() {
try {
foo bar; // constructor of "foo" Class is called.
}
catch (std::runtime_error error) {
cout << error.what() << endl;
getchar();
return EXIT_FAILURE;
}
}
答案 0 :(得分:1)
这是否意味着我遇到了gpu内存泄漏?
是的,确实如此。您不得在未确保最终解除分配的情况下分配资源;如果cudaMemCpy()
失败,则抛出异常 - 你没有做出这样的保证。
OR以某种方式抛出,尝试和捕获可以处理这种情况?
实际上,是的,就像@ Jean-BaptisteYunès所说的那样,RAII是关键。请阅读:
What destructors are run when the constructor throws an exception?
所以,如果你可以将你的内存分配和解除分配推送到你的foo
类的RAII成员中,你就可以完成它的构造,因此它的析构函数 - 解除分配 - 会在退出时运行foo()
范围,即使有例外情况。
此时我会说你正在用你正在编写的一些代码重新发明轮子。您可以找到一种机制,用于包含异常的CUDA错误,以及一个类似于指针的RAII持有者,用于我cuda-api-wrappers库*中已分配的内存。所以你会有类似的东西:
class foo {
public:
using element_type = float;
enum : size_t { num_elements };
protected:
struct {
cuda::memory::device::unique_ptr<element_type> device;
cuda::memory::host::unique_ptr<element_type> host;
} data;
public:
foo() : data( {
cuda::memory::device::make_unique<element_type[]>(
cuda::device::default_device_id,
num_elements
),
cuda::memory::host::make_unique(num_elements)
} )
{
// Do something...
cuda::memory::copy(
data.host.get(), data.device.get(), num_elements * sizeof(element_type)
);
// Do something...
}
~foo() {
// Do something...
// ERRCHK(cudaFree(dev_floatArray));
// No need to free anything! It's magic!
// ERRCHK(cudaDeviceReset());
// Don't reset your device you really need to - and
// you don't need to.
}
}
你可以考虑的另一种方法是Andrei Alexandrescu的“范围保护”机制,而不是用于保存记忆的RAII类。他在这个视频中解释了它(以及它的最新版本):
CppCon 2015: Andrei Alexandrescu - Declarative Control Flow
在任何地方抛出异常是一个好主意,只在main()函数中捕获它们吗?
将其作为一个单独的问题,因为答案不是简单的是/否。实际上,我认为这里有足够的问题和答案可以解决这个问题。
* - 其他库也可能提供类似的东西,例如也许是推力;但是对于这个,你不会受到复杂抽象的束缚,只需要CUDA Runtime API包装器。)