MATLAB的parfor和C++类mex包装器(需要复制构造函数?)

8

我正在尝试使用这里概述的方法,将一个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

我遇到了一个段错误,因为在parfor循环中(每个工作进程中)复制了该类的副本,但由于每个工作进程都是单独的进程,因此C++对象实例未被复制,导致指针无效。
我最初尝试检测每个类方法是否在parfor循环中运行,在这些情况下也重新分配C++对象。然而,由于没有办法检查当前工作进程是否已为对象分配了内存,这会导致多次重新分配,然后仅一次删除(当工作进程退出时),从而导致内存泄漏(请参见问题底部的附录以获取详细信息)。
尝试的解决方案:复制构造函数和使用matlab.mixin.Copyable 在C ++中,处理这种情况的方法是使用复制构造函数(以便在复制包装MATLAB类时只重新分配一次C ++对象)。快速搜索引出了matlab.mixin.Copyable类类型,它似乎提供了所需的功能(即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生成工作者时,似乎没有调用复制构造函数。有没有人能指出我做错了什么,或者是否有某种方法可以实现所需的行为,即在复制MATLAB包装器类时重新初始化C++对象句柄?

备选方案:检测是否在 worker 上运行

仅供参考,这是我正在使用的另一种方法,在 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个回答

7
当您将一个对象实例传递到PARFOR循环的主体中时,其行为就像您将其保存到文件中,然后再次加载。最简单的解决方案可能是将您的cpp_handle_标记为Transient。然后,您需要实现SAVEOBJLOADOBJ来安全地传输数据。有关自定义类的保存/加载行为的更多信息,请参见此页面

谢谢!那个完美地运作了!我已经在问题中发布了更新后的(可工作的)类 :) - Ken Chatfield

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接