我正在尝试使用概述here的方法将一个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
循环中(在每个worker中),但由于每个worker都是一个单独的进程,所以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 生成worker时,复制构造函数是而不是。有没有人对我做错了什么有任何指示,或者在复制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 ++类重新分配多少时间,都会导致内存泄漏。
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
答案 0 :(得分:7)
将对象实例传递到PARFOR
循环体时,行为与将其保存到文件中的行为相同,然后再次加载。最简单的解决方案可能是将cpp_handle_
标记为Transient
。然后,您需要实施SAVEOBJ
和LOADOBJ
来安全地传输您的数据。有关自定义班级的保存/加载行为的详情,请参阅this page。