静态 openCL 类未在使用 boost.python 的 python 模块中正确发布
static openCL class not properly released in python module using boost.python
编辑:好的,所有的编辑都使问题的布局有点混乱,所以我将尝试重写问题(不改变内容,而是改进其结构)。
简而言之,问题
我有一个 openCL 程序,如果我将其编译为可执行文件,则可以正常工作。现在我尝试使用 boost.python
从 Python 调用它。但是,一旦我退出 Python(导入我的模块后),python 就会崩溃。
原因似乎与
程序终止时仅静态存储 GPU 命令队列及其释放机制
MWE和设置
设置
-
使用的IDE:Visual Studio 2015
-
使用的操作系统:视窗 7 64 位
-
蟒蛇版本:3.5
-
AMD OpenCL APP 3.0 标头
-
按照此处的建议直接从 Khronos
cl2.hpp
:空的 openCL 程序抛出弃用警告 -
此外,我有一个带有集成图形硬件的英特尔CPU,没有其他专用显卡
-
我使用编译为 64 位版本的 1.60 版提升库
-
我使用的提升 dll 称为:
boost_python-vc140-mt-1_60.dll
-
没有python的openCL程序工作正常
-
没有 openCL 的 python 模块工作正常
兆威
#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)
行
问题
那么可能是什么原因,可能的解决方案是什么?我怀疑这与我的测试类是静态的事实有关,但由于它适用于可执行文件,我不知是什么导致了它。
我过去遇到过类似的问题。
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();
}
"我希望得到一个答案,向我解释问题到底是什么,以及是否有办法解决它。
首先,我要说的是,doqtor 已经回答了如何解决这个问题——通过确保所有已用 OpenCL 资源的销毁时间定义明确。IMO,这不是"黑客",而是正确的做法。试图依靠静态初始化/清理魔法来做正确的事情 - 并且看着它失败 - 是真正的黑客!
第二,关于这个问题的一些想法:实际问题甚至比常见的静态初始化顺序惨败故事还要复杂。它涉及DLL加载/卸载顺序,既与python在运行时加载自定义dll有关,也与OpenCL的可安装客户端驱动程序(ICD)模型有关。
运行使用 OpenCL 的应用程序/DLL 时涉及哪些 DLL?对于应用程序,唯一相关的 DLL 是链接所针对的opencl.dll
。它在应用程序启动期间(或者当需要 opencl 的自定义 DLL 在 python 中动态加载时)加载到进程内存中。然后,当你第一次在代码中调用clGetPlatformInfo()或类似内容时,ICD逻辑就会启动:opencl.dll
将查找已安装的驱动程序(在Windows中,这些驱动程序在注册表中的某处提到)并动态加载它们各自的dll(使用sth,如LoadLibrary()
系统调用)。例如,这可能是 nvopencl.dll
适用于 NVIDIA,或适用于您已安装的英特尔驱动程序的其他一些 DLL。现在,与相对简单的opencl.dll相比,这个ICD dll可以而且将具有许多依赖关系 - 可能使用Intel IPP,TBB或其他任何东西。所以到现在为止,事情已经变得非常混乱了。
现在,在关闭期间,Windows 加载程序必须决定以哪种顺序卸载哪些 dll。在单个可执行文件中编译示例时,加载/卸载的 dll 的数量和顺序肯定与"python 在运行时加载自定义 dll"方案中不同。这很可能是为什么您仅在后一种情况下遇到问题的原因,并且只有在自定义 dll 关闭期间仍然有一个 opencl-context+命令队列处于活动状态时。队列的销毁(通过 clRelease 触发...在 TestClass 实例的静态销毁期间)委托给英特尔-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,该DLL在其生存期中的某个时刻实例化COM对象。 如果该 DLL 在全局变量中保留对 COM 对象的引用,并且在DLL_PROCESS_DETACH之前不释放 COM 对象,则实现 COM 对象的 DLL 将在 COM 对象的生存期内保留在内存中。 实际上,实现 COM 对象的 DLL 已依赖于保存对 COM 对象的引用的 DLL。 但是加载程序无法知道这种依赖关系。 它只知道DLL被加载到内存中。
现在,我写了很多字,但没有明确的证据证明到底出了什么问题。我从这样的错误中学到的主要教训是:不要进入那个蛇坑,像 doqtor 建议的那样在一个定义明确的地方进行资源清理。晚安。
- 在 python 模块中导入子模块时PyImport_Import失败
- C++ Python 模块在 Blender 中崩溃,但在 Python 控制台中不会崩溃
- 如何使用 swig C++命名空间作为 python 模块公开
- 如何将 cv::mat 对象从 python 模块传递到 c++ 函数并返回返回的 cv::mat 类型为对象?
- C++ Swig Python 模块中的内存泄漏
- boost.python模块扩展生成SIGSEGV
- 使用Boost Python的Python模块是空的?
- 为什么无法在 Cocoa 应用程序调用的 C++ func 中嵌入自定义 Python 模块
- 来自C 的自动化Python模块的张量源
- 如何使用C++导入多个同名的 python 模块
- C++ Python 模块导入错误: "undefined symbol: Py_InitModule3" ( Py_InitModule () )
- 使用 Boost.Python 创建的 Python 模块不会被导入
- 导入 Boost Python 模块 (function_impl_base9max_arityEv) 时出错
- numpy.core.multiarray在通过Xcode中开发的C 应用程序调用Python模块时未能导入
- 运行时错误:找不到与 ImageSensor 匹配的 Python 模块
- GDB Python模块读取内存内容
- 我可以编译boost.python模块而没有BJAM
- C++优化级别是否会影响Swig Python模块的性能
- 如何从python模块(boost.python)导入类
- 使用C/neneneba API和C++类编写Python模块