3

テンプレート化された行列クラスを作成していますが、演算子から値を返すときにスタック オーバーフローが発生します: +、-、* より大きな行列の場合。スタックを解放し、余分なコピーを避けるために、何らかの方法で参照によって戻ることを好みますが、その場合、newで構築されたオブジェクトを返し、「すべてのnewに対して削除を使用する」という一般的なルールを破る必要があります。コピーのオーバーヘッドとスタック制限の問題で値渡しができず、メモリリークで参照渡しもできないのですが、どうすればよいでしょうか。

これが私の製品関数です(マトリックスには2D配列要素が含まれています):

    template<typename T, unsigned int n, unsigned int m> template<unsigned int m2>
Matrix<T,n,m2> Matrix<T,n,m>::operator*(Matrix<T,m,m2>& M) {
    T prod[n][m2];
    if(n*m < GPUAccelerationThreshold)
        for(int i = 0; i < n; i++)
            for(int j = 0; j < m2; j++) {
                prod[i][j] = elems[i][0] * M(0, j); 
                for(int p = 1; p < m; p++)
                    prod[i][j] += elems[i][p] * M(p, j); 
            }
    else {
        array_view<T, 2> product(n, m2, *prod);
        array_view<T, 2> a(n, m, *elems);
        array_view<T, 2> b(m, m2, M.elems[0]);

        parallel_for_each(
            product.extent, 
             [=](index<2> idx) restrict(amp) {
                int row = idx[0];
                int col = idx[1];
                for (int inner = 0; inner < m; inner++) {
                    product[idx] += a(row, inner) * b(inner, col);
                }
            }
        );
        product.synchronize();
    }


    return Matrix<T,n,m2>(prod);
}

GPUで(MSアンプを使用して)いくつかの行列演算を強化したいので、このクラスを書いています。既存のソリューションを検索したところ、GPU で高速化された線形代数ライブラリが見つかりましたが、+、-、* 演算子を使用した単純な行列クラスが見つかりませんでした。多分誰かが私を推薦してくれるでしょうか?

4

3 に答える 3

3

3 つの簡単なコメント:

  • 従来、Matrixクラスは動的割り当てを使用してきました。クラスは表示されませんMatrixが、データが次の場合:
    T myData[n][m];
    
    次のように変更できます。
        std::vector myData;
    
    、コンストラクターでサイズに初期化しn * m、単一のインデックスを計算しますoperator[](境界チェックを行う場合は、プロキシを返す必要があります)。あるいは、要素へのアクセスに を使用することもでき operator()( int i, int j )ます。このソリューションにより、総メモリ使用量がわずかに (ただしごくわずかに) 増加しますが、マトリックスのサイズに関係なく、スタック フットプリントが数十バイトに減少します。 myMatrix( i, j )myMatrix[i][j]
  • また、伝統的に、行列クラスはテンプレート引数の一部として次元を持っていませんでした。これが良いことかどうかは議論の余地があります。ソリューションを使用すると、はるかに優れた型チェック (および実行時ではなくコンパイル時のエラー) が得られますが、ディメンションがテンプレート引数ではなくコンストラクターへの引数である場合は、コマンド ラインまたは構成ファイルなどから読み取ることができます。 . これは、古典的な安全性と柔軟性のトレードオフです。あなたの問題に関して、次元をテンプレートパラメータとして持たないということは、型のすべての行列がT同じ型を持つことを意味します。したがって、メンバー関数から返す行列の内部にアクセスでき、中間はもう必要ありませんT prod[n][m2]。もちろん、すべてのインスタンス化を行うことができますMatrixまたは単にアクセス関数を使用して値を設定します。とにかく、中間は必要ありませんT prod[n][m2]。これは大量のスタック メモリを必要とするだけでなく、結果をコピーする必要があることを意味します。
  • 最後に、これはやや高度です。最適な行列クラスでoperator*は、次の行に沿って行列ではなくヘルパー クラスを返します。R const* myRhs; public: typedef T value_type; MatrixMultiply( L const& lhs, R const& rhs ) : myLhs( &lhs ) , myRhs( &rhs ) { } int getX() const { return myLhs->getX(); } int getY() const { return myRhs->getY(); } T get( int i, int j ) const { return calculateIJ( myLhs, myRhs ); } }; getX()次に、getY()とを使用するテンプレート化されたコンストラクターと代入演算子を提供しますget( i, j )。君の operator*もテンプレートであり、次を返します MatrixMultiply: template MatrixMultiply operator*( L const& lhs, R const& rhs ) { return MatrixMultiply( lhs, rhs ); L::value_type(とが同一でない場合、これはコンパイルされないことに注意してくださいR::value_type。エラー メッセージが明確とはほど遠いことを除いて、これは望ましいことです。) その結果、中間の一時行列を実際に作成することはありません。ご想像のとおり、上記のソリューションは大幅に単純化されています。エラー処理には追加のコードが必要です。並列化は簡単ではないと思います。ただし、複雑な式であっても、すべての中間行列の構築を回避します。MatrixAccessor(純粋な仮想ゲッターを 使用して、抽象基本クラスを使用して、同じ手法を使用できます。Matrixそして、すべてのヘルパーが好きMatrixMultiplyです。私見、これははるかに読みやすく、コンパイラからのエラーメッセージは間違いなくより理解しやすくなります。コンパイラがすべてのメンバー関数を実際にインライン化する限り、結果は同じになります。しかし、重要な関数の入れ子が存在する可能性があるため、これ は大きなifです。)
于 2012-12-29T15:38:12.287 に答える
1

これを解決する簡単な方法はありません。スタック ローカル変数を参照として返すことはできません。変数の「後ろ」にあるメモリは、戻ったときになくなるからです。そのため、どこかに専用のストレージが必要です。新規/削除から来る必要はありませんが、データのコピーを作成するときに何らかのストレージが必要です。

もちろん、1つのソリューションは3つのオペランド操作を持つため、代わりに次のようになります。

a = b + c;

関数を使用します:

追加 (a、b、c);

ここで、a、b、c は参照です。

コードがかなり厄介になりますが、問題を解決するためのより明確な方法は考えられません-独自のアロケーター/削除関数(またはガベージコレクター)を作成したくない場合。

于 2012-12-29T14:59:09.797 に答える
1

実際、私はあなたの考えを完全には理解できません... 二項演算子は 2 つの引数を取り、結果を作成します。実際、新しく作成されたオブジェクトを返していることがわかります。したがって、プログラムを作成する唯一の方法は、メモリを割り当て、使用し、削除することです。実際、私はあなたのコンストラクターが何をするのかさえ理解していません。「prod」へのポインタを単純にコピーすると、関数から返されたときに結果マトリックスが壊れます。これは、関数が戻るとすぐに「prod」メモリが削除されるためです(スタック上に作成されるため)。したがって、参照によって返すこともできません。

マトリックスコンストラクターでメモリを割り当てるためのソリューションを見る方法。行列のサイズに応じてテンプレートとして作成している場合、行列のサイズはテンプレートのパラメーターからわかります (行列のサイズを引数としてテンプレートを作成するのは非常に奇妙だと思います..そのポイントは何ですか?)。したがって、「new」でコンストラクターにメモリを割り当て、「delete」でデストラクタで削除します。したがって、このメモリは、OOP でうまく機能する RAII 方法論に従って割り当てられます。次に、setElement(i, j, value) などのメソッドを実装し、2 項演算子で新しく作成された行列の要素を設定して返します。

ただ、気をつけてほしい問題もあります。コピーコンストラクターは、ポインターだけでなくマトリックスを実際にコピーする必要があります (または、いくつかのデストラクタが同じメモリを破棄しようとします)。または、変更時にマトリックスを実際にコピーする「遅延コピー」モデルをプログラムすることもできます (wiki を参照)。または、コピー コンストラクターを認識せずに非公開にすることもできます (コピーをまったく防止するため)。ライブラリのユーザーに行列の値を変更させたくないという理由で「setElement」などのメソッドの作成が許可されていない場合は、プライベート データとメソッドにアクセスできます (引数として取得したオブジェクトや新しく作成したオブジェクトであっても)。 ) クラスメソッド内にいるため、そのような演算子では。

「else」部分で行われているように、生のポインターを他の計算関数に渡す必要がある場合は、ポインターだけをコピーするポインターからコンストラクターを作成できます (ただし、これは危険な方法です。ポインターを渡す場合は、アクセスしてはなりません)。マトリックスクラスがボスになっているため、どこにもありません)またはデータを完全にコピーします(遅いですが、スタックからポインターを渡すか、後で制御する必要があるポインターを渡すことができます)。マトリックスが破棄されると、デストラクタがデータを消去します。または、「getRawMatrix()」などのプライベート メソッドを作成して、マトリックスからデータへの生のポインターを返し、このポインターを計算関数に渡すか、マトリックス クラスのメソッド内にいるため、単に生データ ポインターを取得することもできます。 .

行列が巨大になる可能性があるため、通常はコンストラクターでメモリを割り当て、「遅延コピー」モデルを作成します。クラス内では、プライベートなデータやメンバーへのアクセスが許可されている、それがクラスです。私はそれが役に立つことを願っています..

于 2012-12-29T15:41:21.357 に答える