0

私は次のコードを使用します

Matrix operator * (const Matrix & obj)
{
    Matrix m = Matrix();

    for (int i = 0; i < 4; i++)
        for (int j = 0; j < 4; j++)
        {
            m[i][j] = 0;

            for (int k = 0; k < 4; k++)
                m[i][j] += _data[i][k] * obj._data[k][j];
        }

    return m;
}

このような

v1 = m_proj * m_view * m_object * v1;

しかし、演算子内で作成した新しいマトリックスが狂ったようにコピーされていると思われるため、これは非常に最適化されていないと思います。私がこれをするなら

Matrix & operator * (const Matrix & obj)
{
    Matrix m = Matrix();

    for (int i = 0; i < 4; i++)
        for (int j = 0; j < 4; j++)
        {
            m[i][j] = 0;

            for (int k = 0; k < 4; k++)
                m[i][j] += _data[i][k] * obj._data[k][j];
        }

    return m;
}

演算子の戻り型をMatrixへの参照に変更すると、コード全体が完全に機能しなくなります(コンパイルされ、行列だけが正しく乗算されません)。

に変更すると

Matrix & operator * (const Matrix & obj)
{
    Matrix & m = * new Matrix();

    for (int i = 0; i < 4; i++)
        for (int j = 0; j < 4; j++)
        {
            m[i][j] = 0;

            for (int k = 0; k < 4; k++)
                m[i][j] += _data[i][k] * obj._data[k][j];
        }

    return m;
}

今は動作しますが、メモリリークがひどいです。

では、どうすればこの問題を解決できますか?エレガントな解決策はありますか?

ありがとう!

4

5 に答える 5

3

関数は新しいオブジェクトを作成しており、オブジェクトを値で返す必要があります。

ローカル変数への参照を返すことはできず、動的に割り当てられたメモリへの参照を返すことには複数の問題があります。それは何も解決しません(式によって作成されるオブジェクトの数を減らさず、動的割り当てのために各オブジェクトの作成コストが高くなります)そしてメモリリークを追加します。

クラスの定義Matrixがないと、メモリが配列で処理されるのか、動的に割り当てられるのかが明確になりません。動的に割り当てられ、C ++ 11コンパイラを使用している場合は、ムーブ構築とムーブ代入を実装でき、これらすべてのコピーのコストが削減されます。

C ++ 03(およびC ++ 11)では、作成されるオブジェクトを実装operator*=して手動で処理できます(これを適切な場所で効率的に実行できると仮定します)。

//v1 = m_proj * m_view * m_object * v1;
Matrix tmp = m_proj;
tmp *= m_view;
tmp *= m_object;
tmp *= v1;
v1 = tmp;

これにより、単一の一時的なものが作成され、すべての乗算が適切に適用され、コピーの数が減ります。

とにかく、4x4マトリックスはコピーするのにそれほど費用がかからないので、私はこれにあまり時間をかけません。

于 2012-10-03T22:43:10.203 に答える
2

2番目のバージョン(参照によって返される自動ストレージ変数)は未定義の動作です-実行しないでください。

3番目のバージョンは不良であり、実行しないでください。可能な場合は動的割り当てを避けてください。

最初のバージョンは、予想よりもそれほど遅くなることはありません。すべての最適化が行われ、コンパイラがRVO / NRVOをサポートしていると仮定します(おそらくサポートします)。

もう1つの方法は、スマートポインターを返すことです。これにより、クラス内で作成されたオブジェクトは、返されるときにコピーされませんが、そのオブジェクトへのマネージポインターがコピーされます(これは簡単にはるかに効率的です)。

于 2012-10-03T22:38:47.527 に答える
1

この質問は、何よりも最適化に関するもののようです。

したがって、コピーの潜在的なオーバーヘッドについて非常に心配しているのに、なぜループを使用しているのかわかりません。

16個の引数を取る行列コンストラクターを作成します。

それで:

Matrix operator * (const Matrix & obj)
{
    return Matrix(
        _data[0][0]*_obj._data[0][0] + _data[0][1]*_obj._data[1][0] + _data[0][2]*_obj._data[2][0] + _data[0][3]*_obj._data[3][0], 
        _data[1][0]*_obj._data[0][0] + _data[1][1]*_obj._data[1][0] + _data[1][2]*_obj._data[2][0] + _data[1][3]*_obj._data[3][0], 
        _data[2][0]*_obj._data[0][0] + _data[2][1]*_obj._data[1][0] + _data[2][2]*_obj._data[2][0] + _data[2][3]*_obj._data[3][0], 
        // etc...
        );
}
于 2012-10-03T22:50:05.063 に答える
1

あなたの最初のケースは正しいものです。コンパイラーはコピーを最適化することができるので(つまり、結果が出るはずの場合は新しいオブジェクトを正しく作成する可能性があります)、実際には効率的かもしれません(...の定義について)。これは「戻り値の最適化」と呼ばれます。

2番目の解決策はゴミです。スタック上のオブジェクトへの参照を返します。これは、関数が戻ると削除されます。なぜそれがあなたに警告さえ与えなかったのだろうか。

観察したように、3番目の解決策は、「新しい」オブジェクトを解放するものがないため、メモリをリークします。RVOが起動した場合、最初の1つよりも効率的ではなく、追加の「動的メモリ割り当て」オーバーヘッドが発生するため、実際には効率が低下します。

乗算を実行し、行列を乗算するためのより適切なAPIを見つけるために演算子*をまったく使用しないことを除いて、これについてできることは多くありません。使用した名前から判断すると、おそらく内部ループから乗算の一部を引き出すことができます。

于 2012-10-03T22:42:23.337 に答える
0

最適化のために、代わりに3つの引数関数を使用できます:void multiply(const Matrix&a、const Matrix&b、Matrix&result)。

このようにして、不要なコピーがないことを確認できます。

于 2012-10-03T22:46:52.977 に答える