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 безопасно транспортировать ваши данные. См.на этой странице подробнее о настройке поведения сохранения/загрузки вашего класса.