我的类是否会在引发异常被捕获时分配GPU内存泄漏?

时间:2017-03-27 17:05:58

标签: exception error-handling cuda resource-leak

我正在写一个"错误检查"遇到错误时,函数with抛出std::runtime_error(下面的代码示例)。

情况:
让我们在foo构造函数中(在下面的文件:fooClass.h示例中)说我能够成功设置cuda设备并分配gpu内存,但我对cudaMemcpy(***)的调用已经返回出于任何原因的错误。这意味着我的errchk(***)函数抛出异常并将控制转移到main()函数(在文件中:Start.cpp)。

因为foo构造函数没有完成它的工作,所以没有创建foo对象而且没有调用~foo()析构函数,因此之前分配的gpu资源没有被释放。

我知道try中的main()块应该以相反的顺序将所有资源解除分配给它们的初始化(每当抛出异常时)。

问题#1:
这是否意味着我遇到了gpu内存泄漏?或者以某种方式throwtrycatch可以处理这种情况?

问题#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;
    }
}

1 个答案:

答案 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包装器。)