cuda调用在析构函数中失败

时间:2016-03-05 14:58:39

标签: c++ stl cuda singleton

我是CUDA的新手,在使用CUDA编写单例/全局变量时遇到了问题。单例分配一些cuda内存并尝试在析构函数中释放它。然而,析构函数崩溃时cudaError为29"驱动程序关闭"。

通过一些搜索,我注意到原因可能是在程序退出后,当CUDA已经关闭时,会调用单例析构函数。

https://github.com/NVlabs/SASSI/issues/4 当在静态成员的析构函数中调用cuda函数时,此链接会报告类似的问题。

https://devtalk.nvidia.com/default/topic/457922/cudafree-crash-in-destructor-when-exit-is-called/ 此链接报告相同的问题和不明确的解决方案。

老实说,我没有太多的CUDA知识,所以我想请求一些详细的解释和正式解决这个问题。

编辑:

感谢@Robert Crovella的提醒,我做了一些测试来重现这个问题。好的,我发现这个问题发生在std :: unordered_map或std :: map的单例和全局变量中,它们在其值对象的析构函数中调用cuda。

工作代码,没有使用std :: map:

#include <iostream>
#include <map>

#define CUDA_CHECK(x) std::cerr << (x) << std::endl;

class cuda_user
{   
    char* data;
public:
    cuda_user() {
        std::cerr << "constr" << std::endl;
        CUDA_CHECK(cudaMalloc((void**)&data, 1024));
    }
    void foo() {
        std::cerr << "foo" << std::endl;
    };
    ~cuda_user() {
        std::cerr << "destr"  << std::endl;
        CUDA_CHECK(cudaFree(data));
    }
};

cuda_user cu;
int main()
{   
    cu.foo();
}

输出:

constr
0
foo
destr
0

崩溃的代码,使用相同的cuda_user clas,但使用了std :: map:

#include <iostream>
#include <map>

#define CUDA_CHECK(x) std::cerr << (x) << std::endl;

class cuda_user
{   
    char* data;
public:
    cuda_user() {
        std::cerr << "constr" << std::endl;
        CUDA_CHECK(cudaMalloc((void**)&data, 1024));
    }
    void foo() {
        std::cerr << "foo" << std::endl;
    };
    ~cuda_user() {
        std::cerr << "destr"  << std::endl;
        CUDA_CHECK(cudaFree(data));
    }
};

std::map<int, cuda_user> map;
int main()
{   
    map[1].foo();
}

输出:

constr
0
foo
destr
29 << Error!

更新

我在CentOS 6.3上使用gcc48和nvcc75

1 个答案:

答案 0 :(得分:4)

[将评论扩展为摘要答案]

您的代码在不知不觉中依赖于未定义的行为(转换单元对象的破坏顺序),除了在析构函数中显式控制包含CUDA运行时API调用的对象和生命周期之外,没有真正的解决方法,或者只是避免使用这些API完全在析构函数中调用。

详细说明:

由nvcc调用的CUDA前端默默地添加了许多样板代码和翻译单元范围对象,这些对象执行CUDA上下文设置和拆卸。在依赖于CUDA上下文的任何API调用之前,该代码必须才能运行。如果在析构函数中包含CUDA运行时API调用的对象在上下文被拆除后调用API,则代码可能会因运行时错误而失败。当对象超出范围时,C ++不定义破坏的顺序。在CUDA上下文被拆除之前,您的单例或对象需要被销毁,但不能保证会发生。这实际上是未定义的行为。

您可以在此ASCII table中看到更完整的示例(在内核启动的上下文中)。