【问题标题】:static openCL class not properly released in python module using boost.python使用 boost.python 在 python 模块中未正确释放静态 openCL 类
【发布时间】:2016-04-29 06:17:32
【问题描述】:

编辑:好的,所有的编辑都让问题的布局有点混乱,所以我会尝试重写问题(不改变内容,但改进其结构)。

简而言之问题

如果我将它编译为可执行文件,我有一个运行良好的 openCL 程序。现在我尝试使用boost.python 使其可从Python 调用。但是,一旦我退出 Python(在导入我的模块之后),python 就会崩溃。

原因似乎与此有关

在程序终止时仅静态存储 GPU CommandQueues 及其释放机制

MWE 和设置

设置

  • 使用的 IDE:Visual Studio 2015

  • 使用的操作系统:Windows 7 64bit

  • Python 版本:3.5

  • AMD OpenCL APP 3.0 标头

  • cl2.hpp 直接来自 Khronos,如下所示:empty openCL program throws deprecation warning

  • 我还有一个带有集成显卡硬件的 Intel CPU,没有其他专用显卡

  • 我使用 1.60 版本的 boost 库编译为 64 位版本

  • 我使用的boost dll叫做:boost_python-vc140-mt-1_60.dll

  • 不用python的openCL程序也能正常运行

  • 没有openCL的python模块可以正常工作

MWE

#include <vector>

#define CL_HPP_ENABLE_EXCEPTIONS
#define CL_HPP_TARGET_OPENCL_VERSION 200
#define CL_HPP_MINIMUM_OPENCL_VERSION 200 // I have the same issue for 100 and 110
#include "cl2.hpp"
#include <boost/python.hpp>

using namespace std;

class TestClass
{
private:
    std::vector<cl::CommandQueue> queues;
    TestClass();

public:
    static const TestClass& getInstance()
    {
        static TestClass instance;
        return instance;
    }
};

TestClass::TestClass()
{
    std::vector<cl::Device> devices;
    vector<cl::Platform> platforms;

    cl::Platform::get(&platforms);

    //remove non 2.0 platforms (as suggested by doqtor)
    platforms.erase(
        std::remove_if(platforms.begin(), platforms.end(),
            [](const cl::Platform& platform)
    {
        int v = cl::detail::getPlatformVersion(platform());
        short version_major = v >> 16;
        return !(version_major >= 2);
    }),
        platforms.end());

    //Get all available GPUs
    for (const cl::Platform& pl : platforms)
    {
        vector<cl::Device> plDevices;
        try {
            pl.getDevices(CL_DEVICE_TYPE_GPU, &plDevices);
        }
        catch (cl::Error&)
        {

            // Doesn't matter. No GPU is available on the current machine for 
            // this platform. Just check afterwards, that you have at least one
            // device
            continue;
        }       
        devices.insert(end(devices), begin(plDevices), end(plDevices));
    }

    cl::Context context(devices[0]);
    cl::CommandQueue queue(context, devices[0]);

    queues.push_back(queue);
}

int main()
{
    TestClass::getInstance();

    return 0;
}

BOOST_PYTHON_MODULE(FrameWork)
{
    TestClass::getInstance();
}

调用程序

所以在将程序编译为dll 之后,我启动 python 并运行以下程序

import FrameWork
exit()

虽然导入没有问题,但 python 在 exit() 上崩溃。所以我点击调试,Visual Studio 告诉我以下代码部分(在cl2.hpp)中有一个异常:

template <>
struct ReferenceHandler<cl_command_queue>
{
    static cl_int retain(cl_command_queue queue)
    { return ::clRetainCommandQueue(queue); }
    static cl_int release(cl_command_queue queue)  //  --  HERE  --
    { return ::clReleaseCommandQueue(queue); }
};

如果您将上述代码编译为简单的可执行文件,则它可以正常工作。如果满足以下条件之一,该代码也可以工作:

  • CL_DEVICE_TYPE_GPU 替换为CL_DEVICE_TYPE_ALL

  • queues.push_back(queue) 行被删除

问题

那么这可能是什么原因以及可能的解决方案是什么?我怀疑这与我的测试类是静态的这一事实有关,但由于它与可执行文件一起使用,我不知道是什么原因造成的。

【问题讨论】:

  • 请仔细阅读发布指南,有一堆信息丢失。
  • @UlrichEckhardt 添加了 MWE。这对您有帮助吗?
  • 我不需要帮助,你需要。您的示例可能不是最小的,三个文件是一堆。缺少其他信息。

标签: python c++ boost opencl boost-python


【解决方案1】:

我过去也遇到过类似的问题。

OpenCL1.2 支持clRetain* 函数。 在为第一个 GPU 平台(platforms[0].getDevices(...)CL_DEVICE_TYPE_GPU)获取设备时,它必须恰好是OpenCL1.2 之前的平台,因此您会崩溃。当获得任何类型的设备(GPU/CPU/...)时,您的第一个平台将变为 OpenCL1.2+,一切都很好。

修复问题集:

#define CL_HPP_MINIMUM_OPENCL_VERSION 110

这将确保不会针对不受支持的平台(OpenCL 1.2 之前)调用clRetain*


更新:我认为cl2.hpp 中存在一个错误,尽管将最低 OpenCL 版本设置为 1.1,但在创建命令队列时仍会尝试在前OpenCL1.2 设备上使用clRetain*。 将最低 OpenCL 版本设置为 110 和版本过滤对我来说很好。

完整的工作示例:

#include "stdafx.h"
#include <vector>

#define CL_HPP_ENABLE_EXCEPTIONS
#define CL_HPP_TARGET_OPENCL_VERSION 200
#define CL_HPP_MINIMUM_OPENCL_VERSION 110
#include <CL/cl2.hpp>

using namespace std;

class TestClass
{
private:
    std::vector<cl::CommandQueue> queues;
    TestClass();

public:
    static const TestClass& getInstance()
    {
        static TestClass instance;
        return instance;
    }
};

TestClass::TestClass()
{
    std::vector<cl::Device> devices;
    vector<cl::Platform> platforms;

    cl::Platform::get(&platforms);

    size_t x = 0;
    for (; x < platforms.size(); ++x)
    {
        cl::Platform &p = platforms[x];
        int v = cl::detail::getPlatformVersion(p());
        short version_major = v >> 16;
        if (version_major >= 2) // OpenCL 2.x
            break;
    }
    if (x == platforms.size())
        return; // no OpenCL 2.0 platform available

    platforms[x].getDevices(CL_DEVICE_TYPE_GPU, &devices); 
    cl::Context context(devices);
    cl::CommandQueue queue(context, devices[0]);

    queues.push_back(queue); 
}

int main()
{
    TestClass::getInstance();
    return 0;
}

更新2

那么这可能是什么原因以及可能的解决方案是什么? 我怀疑这与我的测试类是 静态的,但由于它适用于可执行文件,我不知道是什么 导致它。

TestClass 静态似乎是一个原因。从 python 运行时,看起来释放内存的顺序错误。要解决这个问题,您可能需要添加一个必须显式调用才能在 python 开始释放内存之前释放 opencl 对象的方法。

static TestClass& getInstance() // <- const removed
{
    static TestClass instance;
    return instance;
}

void release()
{
    queues.clear();
}

BOOST_PYTHON_MODULE(FrameWork)
{
    TestClass::getInstance();
    TestClass::getInstance().release();
}

【讨论】:

  • 但是我所有的设备都是openCL 2.0,我什至将最低版本设置为200。还是我误解了你?
  • 将 CL_HPP_MINIMUM_OPENCL_VERSION 更改为 110 或 100 并检查。
  • 所以我更改为CL_HPP_MINIMUM_OPENCL_VERSION 110100,当我输入exit() 时,python 两次都崩溃了。同样在我的真实代码中,我实际上手动删除了所有平台
  • 感谢您的更新。不幸的是,这无济于事。你要做的是编译一个独立的可执行文件。这从一开始就对我有用。如果我尝试编译为 dll 并将其导入 Python,我只有会遇到问题。独立的可执行文件对我来说没有错误。我会更新我的问题,试图让一切更清楚。
  • @MrZ CommandQueue 对象的显式发布应该可以修复 python 崩溃 - 请参阅我的最新更新。
【解决方案2】:

“我希望得到一个能向我解释问题究竟是什么以及是否有解决方法的答案。”

首先,让我说 doqtor 已经回答了如何解决这个问题——通过确保所有使用的 OpenCL 资源的明确销毁时间。 IMO,这不是“黑客”,而是正确的做法。试图依靠静态初始化/清理魔法来做正确的事情——但眼睁睁地看着它做不到——才是真正的 hack!

第二,关于问题的一些思考:实际问题比常见的静态初始化订单惨败故事还要复杂。它涉及 DLL 加载/卸载顺序,既与 python 在运行时加载您的自定义 dll 相关,又与(更重要的是)与 OpenCL 的可安装客户端驱动程序 (ICD) 模型相关。

运行使用 OpenCL 的应用程序/dll 时涉及哪些 DLL?对于应用程序,唯一相关的 DLL 是您链接的 opencl.dll。它在应用程序启动期间加载到进程内存中(或者当需要 opencl 的自定义 DLL 在 python 中动态加载时)。 然后,当您第一次在代码中调用 clGetPlatformInfo() 或类似方法时,ICD 逻辑就会启动:opencl.dll 将查找已安装的驱动程序(在 Windows 中,这些驱动程序在注册表中的某处提到)并动态加载它们各自的 dll (使用类似LoadLibrary() 系统调用的东西)。这可能是例如nvopencl.dll 用于 nvidia,或其他一些用于您已安装的英特尔驱动程序的 dll。现在,与相对简单的 opencl.dll 相比,这个 ICD dll 可以并且将拥有大量的依赖项——可能使用英特尔 IPP、TBB 或其他。所以到现在为止,事情已经变得非常混乱了。

现在,在关机期间,Windows 加载程序必须决定以何种顺序卸载哪些 dll。当您在单个可执行文件中编译示例时,加载/卸载的 dll 的数量和顺序肯定会不同于“python 在运行时加载您的自定义 dll”场景。这很可能是您仅在后一种情况下遇到问题的原因,并且只有在您的自定义 dll 关闭期间您仍然有一个 opencl-context+commandqueue 活动时。您的队列的销毁(通过 clRelease... 在您的 testclass 实例的静态销毁期间触发)被委托给 intel-icd-dll,因此该 dll 在当时必须仍然可以正常工作。如果由于某种原因不是这种情况(可能是因为加载程序选择卸载它或它需要的 dll 之一),你就会崩溃。

这个思路让我想起了这篇文章:

https://blogs.msdn.microsoft.com/larryosterman/2004/06/10/dll_process_detach-is-the-last-thing-my-dlls-going-to-see-right/

有一段讲“COM 对象”,它可能同样适用于“OpenCL 资源”:

“因此,请考虑这样一种情况:您有一个 DLL,它在其生命周期的某个时间点实例化一个 COM 对象。如果该 DLL 在全局变量中保留对 COM 对象的引用,并且不释放 COM直到 DLL_PROCESS_DETACH,然后实现 COM 对象的 DLL 将在 COM 对象的生命周期内保存在内存中。实际上,实现 COM 对象的 DLL 已经变得依赖于持有对 COM 对象的引用的 DLL。但是loader 无法知道这种依赖关系。它所知道的只是 DLL 被加载到内存中。”


现在,我写了很多字,却没有得出确切的证据来证明到底出了什么问题。我从这些错误中学到的主要教训是:不要进入那个蛇坑,并在 doqtor 建议的明确定义的地方进行资源清理。晚安。

【讨论】:

  • 嗯,现在在你的代码中包含 new 和 delete 是不受欢迎的,RAII 是首选,这样当变量超出范围时清理会自动发生,但我猜在 DLL 级别仍然存在卸载时需要手动清理,直到将来某个时候,我们可能会对超出范围的链接库有类似的概念。
猜你喜欢
  • 2012-05-08
  • 1970-01-01
  • 2011-05-07
  • 1970-01-01
  • 1970-01-01
  • 2022-11-03
  • 1970-01-01
  • 2013-08-26
  • 1970-01-01
相关资源
最近更新 更多