8

我正在尝试使用此处概述的方法将 C++ 类包装在 matlab mex 包装器中。基本上,我有一个初始化 mex 文件,它返回一个 C++ 对象句柄:

handle = myclass_init()

然后我可以将它传递给另一个myclass_amethod使用句柄调用类方法的 mex 文件(例如),然后最终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++ 中,处理这种情况的方法是复制构造函数(这样 C++ 对象只会在复制包装 MATLAB 类时重新分配一次)。快速搜索一下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++ 对象句柄的期望行为?


替代方法:检测何时在工作人员上运行

仅供参考,这是我在工作人员中使用的重新分配的替代方法:

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

从上面可以看出,当在 worker 中运行时,确实会发生重新分配。但是,无论 C++ 类被重新分配多少次,每个 worker 只调用一次析构函数,从而导致内存泄漏。


解决方案:使用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
4

1 回答 1

7

当您将对象实例传递到PARFOR循环体时,其行为与将其保存到文件中然后再次加载它的行为相同。最简单的解决方案可能是将您的标记cpp_handle_Transient. 然后,您需要实施SAVEOBJLOADOBJ安全地传输您的数据。有关自定义类的保存/加载行为的更多信息,请参阅此页面

于 2012-12-12T15:55:27.777 回答