8

ここで概説するアプローチを使用して、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
4

1 に答える 1

7

オブジェクトインスタンスをPARFORループの本体に渡すときの動作は、オブジェクトをファイルに保存してから再度ロードした場合と同じです。cpp_handle_最も簡単な解決策は、おそらくあなたをとしてマークすることTransientです。次に、データを実装SAVEOBJLOADOBJて安全に転送する必要があります。クラスの保存/読み込み動作のカスタマイズの詳細については、このページを参照してください。

于 2012-12-12T15:55:27.777 に答える