ここで概説するアプローチを使用して、C++クラスをmatlabmexラッパーでラップしようとしています。基本的に、C++オブジェクトハンドルを返す初期化mexファイルがあります。
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++オブジェクトも再割り当てします。ただし、オブジェクトが現在のワーカーに割り当てられているかどうかを確認する方法がまだないため、複数の再割り当てが発生し、1回の削除(ワーカーの終了時)によってメモリリークが発生します(下部の付録を参照)。詳細については質問)。
試みられた解決策:コンストラクターをコピーして使用するmatlab.mixin.Copyable
C ++では、これを処理する方法はコピーコンストラクターです(ラッパーのMATLABクラスがコピーされるときにC ++オブジェクトが再割り当てされるのは1回だけです)。クイック検索により、必要な機能(つまり、MATLABハンドルクラスのディープコピー)を提供しているように見えるmatlab.mixin.Copyableクラスタイプが表示されます。したがって、私は次のことを試しました。
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
上記のように、ワーカーで実行しているときに再割り当てが実際に発生するようになりました。ただし、デストラクタは、C ++クラスが再割り当てされた回数に関係なく、すべてのワーカーに対して1回だけ呼び出されるため、メモリリークが発生します。
解決策:を使用する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