MATLAB parfor и C++ class mex wrappers (требуется конструктор копирования?)
я пытаюсь обернуть класс C++ в оболочку matlab mex, используя описанный подход здесь. В принципе, у меня есть файл инициализации mex, который возвращает дескриптор объекта C++:
handle = myclass_init()
затем я могу передать это в другой файл mex (например,myclass_amethod
), которые используют дескриптор для вызова методов класса, а затем в конечном счете до myclass_delete
чтобы освободить объект C++:
retval = myclass_amethod(handle, parameter)
myclass_delete(handle)
я завернул это в класс MATLAB для удобства использовать:
classdef myclass < handle
properties(SetAccess=protected)
cpp_handle_
end
methods
% class constructor
function obj = myclass()
obj.cpp_handle_ = myclass_init();
end
% class destructor
function delete(obj)
myclass_delete(obj.cpp_handle_);
end
% class method
function amethod(parameter)
myclass_amethod(obj.cpp_handle_, parameter);
end
end
end
проблема: это не работает в параллельном коде
это прекрасно работает в параллельном коде. Однако, как только я назову его изнутри parfor
:
cls = myclass();
parfor i = 1:10
cls.amethod(i)
end
я получаю segfault, так как копия класса сделана в parfor
цикл (в каждом работнике), но поскольку каждый работник является отдельным процессом, экземпляр объекта C++ -не скопировано, что приводит к недопустимому указателю.
сначала я пытался определить, когда каждый класс метод работал в цикле parfor, и в этих случаях перераспределить объект c++ тоже. Однако, поскольку нет способа проверить, был ли объект выделен для текущего работника еще или нет, это приводит к нескольким перераспределениям, а затем только к одному удалению (когда работник выходит), что приводит к утечке памяти (см. приложение в нижней части вопроса для деталей).
попытка решения: копирование конструкторов и использование matlab.mixin.Copyable
в C++ способ справиться с этим будет скопируйте конструкторы (так что объект C++ перераспределяется только один раз при копировании класса оболочки MATLAB). Быстрый поиск вызывает matlab.миксин.Копировать тип класса, который, по-видимому, обеспечивает необходимую функциональность (т. е. глубокие копии классов дескрипторов MATLAB). Поэтому я попробовал следующее:
classdef myclass < matlab.mixin.Copyable
properties(SetAccess=protected)
cpp_handle_
end
methods
function obj = myclass(val)
if nargin < 1
% regular constructor
obj.cpp_handle_ = rand(1);
disp(['Initialized myclass with handle: ' num2str(obj.cpp_handle_)]);
else
% copy constructor
obj.cpp_handle_ = rand(1);
disp(['Copy initialized myclass with handle: ' num2str(obj.cpp_handle_)]);
end
end
% destructor
function delete(obj)
disp(['Deleted myclass with handle: ' num2str(obj.cpp_handle_)]);
end
% class method
function amethod(obj)
disp(['Class method called with handle: ' num2str(obj.cpp_handle_)]);
end
end
end
тестирование этого класса, как и выше, т. е.:
cls = myclass();
parfor i = 1:10
cls.amethod(i)
end
результат:
Initialized myclass with handle: 0.65548
Class method called with handle: 0.65548
Class method called with handle: 0.65548
Class method called with handle: 0.65548
Class method called with handle: 0.65548
Class method called with handle: 0.65548
Class method called with handle: 0.65548
Class method called with handle: 0.65548
Class method called with handle: 0.65548
Class method called with handle: 0.65548
Class method called with handle: 0.65548
Deleted myclass with handle: 0.65548
Deleted myclass with handle: 0.65548
Deleted myclass with handle: 0.65548
другими словами, это кажется, что конструктор копирования не вызывается при нересте рабочих для parfor. Есть ли у кого-нибудь какие-либо указатели на то, что я делаю неправильно, или есть ли способ достичь желаемого поведения повторной инициализации дескриптора объекта C++ при копировании класса-оболочки MATLAB?
альтернативный подход: обнаружение при работе на worker
просто для справки, вот альтернативный подход, который я использую для перераспределения у рабочего:
classdef myclass < handle
properties(SetAccess=protected)
cpp_handle_
end
methods
function obj = myclass(val)
obj.cpp_handle_ = rand(1);
disp(['Initialized myclass with handle: ' num2str(obj.cpp_handle_)]);
end
% destructor
function delete(obj)
disp(['Deleted myclass with handle: ' num2str(obj.cpp_handle_)]);
end
% class method
function amethod(obj)
obj.check_handle()
disp(['Class method called with handle: ' num2str(obj.cpp_handle_)]);
end
% reinitialize cpp handle if in a worker:
function check_handle(obj)
try
t = getCurrentTask();
% if 'getCurrentTask()' returns a task object, it means we
% are running in a worker, so reinitialize the class
if ~isempty(t)
obj.cpp_handle_ = rand(1);
disp(['cpp_handle_ reinitialized to ' num2str(obj.cpp_handle_)]);
end
catch e
% in case of getCurrentTask() being undefined, this
% probably simply means the PCT is not installed, so
% continue without throwing an error
if ~strcmp(e.identifier, 'MATLAB:UndefinedFunction')
rethrow(e);
end
end
end
end
end
и вывод:
Initialized myclass with handle: 0.034446
cpp_handle_ reinitialized to 0.55625
Class method called with handle: 0.55625
cpp_handle_ reinitialized to 0.0048098
Class method called with handle: 0.0048098
cpp_handle_ reinitialized to 0.58711
Class method called with handle: 0.58711
cpp_handle_ reinitialized to 0.81725
Class method called with handle: 0.81725
cpp_handle_ reinitialized to 0.43991
cpp_handle_ reinitialized to 0.79006
cpp_handle_ reinitialized to 0.0015995
Class method called with handle: 0.0015995
cpp_handle_ reinitialized to 0.0042699
cpp_handle_ reinitialized to 0.51094
Class method called with handle: 0.51094
Class method called with handle: 0.0042699
Class method called with handle: 0.43991
cpp_handle_ reinitialized to 0.45428
Deleted myclass with handle: 0.0042699
Class method called with handle: 0.79006
Deleted myclass with handle: 0.43991
Deleted myclass with handle: 0.79006
Class method called with handle: 0.45428
как видно выше, перераспределение действительно теперь происходит при запуске в работнике. Однако деструктор вызывается только один раз для каждого работника независимо от того, сколько раз класс C++ был перераспределен, что приводит к утечке памяти.
решение: используя loadobj
следующие работы:
classdef myclass < handle
properties(SetAccess=protected, Transient=true)
cpp_handle_
end
methods(Static=true)
function obj = loadobj(a)
a.cpp_handle_ = rand(1);
disp(['Load initialized encoder with handle: ' num2str(a.cpp_handle_)]);
obj = a;
end
end
methods
function obj = myclass(val)
obj.cpp_handle_ = rand(1);
disp(['Initialized myclass with handle: ' num2str(obj.cpp_handle_)]);
end
% destructor
function delete(obj)
disp(['Deleted myclass with handle: ' num2str(obj.cpp_handle_)]);
end
% class method
function amethod(obj)
disp(['Class method called with handle: ' num2str(obj.cpp_handle_)]);
end
end
end
1 ответов
когда вы передаете экземпляр объекта в тело PARFOR
петли, поведение такое же, как если бы вы сохранили его в файл, а затем снова зарядил его. Самое простое решение, вероятно, отметить ваш cpp_handle_
as Transient
. Тогда вам нужно реализовать SAVEOBJ
и LOADOBJ
безопасно транспортировать ваши данные. См.на этой странице подробнее о настройке поведения сохранения/загрузки вашего класса.