статический класс openCL, который не был правильно выпущен в модуле python, используя boost.python

EDIT: Хорошо, все изменения сделали макет вопроса немного запутанным, поэтому я попытаюсь переписать вопрос (не меняя контент, а улучшая его структуру).

Кратко о проблеме

У меня есть программа openCL, которая отлично работает, если я скомпилирую ее как исполняемый файл. Теперь я пытаюсь сделать его вызываемым из Python, используя boost.python . Однако, как только я выхожу из Python (после импорта моего модуля), python падает.

Причина, похоже, имеет отношение к

статически сохраняя только GPU CommandQueues и механизм их выпуска, когда программа завершается

MWE и настройка

Настроить

  • IDE: Visual Studio 2015

  • Используемая ОС: Windows 7 64bit

  • Версия для Python: 3.5

  • Заголовки AMD OpenCL APP 3.0

  • cl2.hpp прямо из Khronos, как предлагается здесь: пустая программа openCL выдает предупреждение об отставке

  • Также у меня есть процессор Intel со встроенным графическим оборудованием и никакой другой выделенной графической картой

  • Я использую версию 1.60 библиотеки boost, скомпилированную как 64-разрядные версии

  • Используемый boost_python-vc140-mt-1_60.dll dll называется boost_python-vc140-mt-1_60.dll

  • Программа openCL без python отлично работает

  • Модуль python без openCL отлично работает

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() . Поэтому я нажимаю на debug, и 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) удаляется

Вопрос

Итак, какова может быть причина этого и какие возможные решения? Я подозреваю, что это связано с тем, что мой тестовый класс статичен, но поскольку он работает с исполняемым файлом, я не понимаю, что его вызывает.

2 Solutions collect form web for “статический класс openCL, который не был правильно выпущен в модуле python, используя boost.python”

Раньше я сталкивался с подобной проблемой.

clRetain* поддерживаются в OpenCL1.2 . При получении устройств для первой платформы GPU ( platforms[0].getDevices(...) для CL_DEVICE_TYPE_GPU ) в вашем случае это должно быть платформа pre OpenCL1.2 следовательно, вы получаете сбой. При получении устройств любого типа (GPU / CPU / …) ваша первая платформа меняется как OpenCL1.2 +, и все в порядке.

Чтобы устранить проблему:

#define CL_HPP_MINIMUM_OPENCL_VERSION 110

Это гарантирует, что вызовы clRetain* не будут созданы для неподдерживаемых платформ (pre OpenCL 1.2)


Обновление . Я думаю, что в cl2.hpp есть ошибка, которая, несмотря на то, что при минимальной версии OpenCL версии 1.1 она все еще пытается использовать clRetain* на устройствах pre OpenCL1.2 при создании командной очереди. Настройка минимальной версии 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. Чтобы исправить это, вы можете добавить метод, который должен быть явно вызван для освобождения opencl-объектов до того, как python начнет выпускать память.

 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. ИМО, это не «взлом», а правильная вещь. Пытаясь полагаться на статическую магию инициализации / очистки, чтобы делать правильные вещи – и смотреть, как это не получается, – это настоящий взлом!

Во-вторых, некоторые соображения по поводу проблемы: актуальная проблема еще сложнее, чем общие истории фиаско. Он включает в себя порядок загрузки / выгрузки DLL, как в связи с загрузкой python вашей пользовательской dll во время выполнения, так и (что более важно) с помощью модели устанавливаемого клиента OpenCL (ICD).

Какие DLL-файлы задействованы при запуске приложения / dll, использующего OpenCL? Для приложения единственной соответствующей DLL является opencl.dll вы opencl.dll . Он загружается в память процесса во время запуска приложения (или когда ваша пользовательская DLL, которая нуждается в opencl, динамически загружается в python). Затем в то время, когда вы сначала вызываете clGetPlatformInfo () или похожим в своем коде, логика ICD opencl.dll в: opencl.dll будет искать установленные драйверы (в окнах, которые упоминаются где-то в реестре) и динамически загружать их соответствующие DLL (используя sth, как системный вызов LoadLibrary() ). Это может быть, например, nvopencl.dll для nvidia или какой-либо другой dll для установленного драйвера Intel. Теперь, в отличие от относительно простой opencl.dll, эта библиотека ICD может и будет иметь множество зависимостей самостоятельно – возможно, используя Intel IPP, или TBB, или что-то еще. Итак, к настоящему времени все стало уже бесполезно.

Теперь во время выключения загрузчик окон должен решить, какие DLL для разгрузки в каком порядке. Когда вы компилируете свой пример в одном исполняемом файле, количество и порядок загрузки / выгрузки dll будут, разумеется, разными, чем в сценарии «python загружает вашу пользовательскую DLL во время выполнения». И это вполне может быть причиной, по которой вы сталкиваетесь с проблемой только в последнем случае, и только в том случае, если у вас все еще есть opencl-context + commandqueue во время выключения вашей пользовательской dll. Уничтожение вашей очереди (вызванное с помощью 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, тогда DLL, которая реализует COM-объект, будет храниться в памяти в течение всего жизненного цикла COM-объекта. Эффективно DLL, реализующая объект COM, становится зависимой от DLL, которая содержит ссылку на COM-объект. Но загрузчик не знает об этой зависимости. Все, что он знает, это то, что DLL загружаются в память ».


Теперь я написал много слов, не прибегая к окончательному доказательству того, что на самом деле происходит не так. Главный урок, который я узнал из таких ошибок: не входите в эту змеиную яму и делайте очистку ресурсов в определенном месте, как предлагал doqtor. Доброй ночи.

  • «Развертывание» рекурсивной функции?
  • ОШИБКА: pyopencl: создание контекста для определенного устройства
  • Не удалось загрузить OpenCL runtime - OpenCV3 Python
  • Python - лучший язык программирования в мире.