6

私はいくつかの C++ 計算力学を行っています (心配しないでください。ここでは物理学の知識は必要ありません) が、本当に気になることがあります。

3D 数学ベクトルを表現したいとします (std::vector とは関係ありません):

class Vector {
    public:
        Vector(double x=0., double y=0., double z=0.) { 
            coordinates[0] = x;
            coordinates[1] = y;
            coordinates[2] = z;
        }
    private:
        double coordinates[3];
};

ここまでは順調ですね。これで、operator[] をオーバーロードして座標を抽出できます。

double& Vector::operator[](int i) {
     return coordinates[i] ;
}

したがって、次のように入力できます。

Vector V; 

… //complex computation with V

double x1 = V[0];
V[1] = coord2;

問題は、ここでは 0 からのインデックス付けが自然ではないことです。つまり、配列をソートするときは気にしませんが、実際には、すべての紙、本、またはその他の慣習的な表記法は、常に 1 から始まる座標を部分分解しています。私たちが何をしているのかを理解するには、常に二重のテイクが必要です。もちろん、これは行列では最悪です。

明らかな解決策の 1 つは、わずかに異なるオーバーロードです。

double& Vector::operator[](int i) {
     return coordinates[i-1] ;
}

入力できるように

double x1 = V[1];
V[2] = coord2;

この i-1 減算は、小さなオーバーヘッドの良い候補と思われます。非常に小さいと言うかもしれませんが、私は計算力学を行っているため、通常、これは余裕のないものです。

だから今(最後に)私の質問:コンパイラはこれを最適化できると思いますか、それとも最適化する方法はありますか?(テンプレート、マクロ、ポインター、または参照クラッジ...)

論理的には、

double xi = V[i];

ほとんどの場合、ブラケット間の整数はリテラルです (ループの 3 回反復を除く)。

(この長い質問で申し訳ありません)

編集:

すべてのコメントと回答に感謝します

インデックスが 0 のベクトルに慣れているという人々の意見には同意しません。オブジェクト指向の観点からは、0 インデックスの配列で実装されているため、数学 Vector を 0 インデックスにする理由はないと思います。基礎となる実装を気にする必要はないと思われます。ここで、パフォーマンスを気にせず、マップを使用して Vector クラスを実装するとします。次に、「1」を「1番目」の座標にマッピングするのが自然だと思います。

とは言っても、私は 1 インデックスのベクトルと行列を試してみましたが、いくつかのコードを書いた後、配列を使用するたびにうまく相互作用しないことがわかりました。Vector とコンテナー (std::array、std::vector...) は頻繁に対話することはないと思いますが (相互にデータを転送することを意味します)、それは間違っていたようです。

今、私はあまり物議を醸していないと思う解決策を持っています(あなたの意見を教えてください):物理的なコンテキストでベクターを使用するたびに、列挙型を使用することを考えます:

enum Coord {
    x = 0,
    y = 1,
    z = 2
};
Vector V;
V[x] = 1;

私が見る唯一の欠点は、これらの x、y、および z が警告なしで再定義できることです...

4

6 に答える 6

11

これは、逆アセンブリを見て測定または検証する必要がありますが、私の推測では、ゲッター関数は小さく、その引数は定数です。コンパイラが関数をインライン化し、減算を定数倍にする可能性が高くなります。その場合、ランタイム コストはゼロになります。

于 2012-08-07T22:45:42.327 に答える
1

実際にプロファイリングしたり、生成されたコードを調べたりしましたか? それがこの質問への回答です。

operator[] の実装が表示されている場合、これはオーバーヘッドがゼロになるように最適化されている可能性があります。

于 2012-08-07T22:55:32.900 に答える
1

ベンチマークなしでは、パフォーマンスについて客観的なことは何も言えません。x86 では、この減算は、非常に安価な相対アドレッシングを使用してコンパイルできます。がインライン化されている場合operator[]、オーバーヘッドはゼロです。これはinline、GCC の__attribute__((always_inline)).

それを保証する必要があり、オフセットがコンパイル時の定数である場合は、テンプレートを使用する方法があります。

template<size_t I>
double& Vector::get() {
    return coordinates[i - 1];
}

double x = v.get<1>();

すべての実用的な目的で、これは一定の折り畳みのおかげでオーバーヘッドがゼロであることが保証されています。名前付きアクセサーを使用することもできます。

double Vector::x() const { return coordinates[0]; }
double Vector::y() const { return coordinates[1]; }
double Vector::z() const { return coordinates[2]; }

double& Vector::x() { return coordinates[0]; }
double& Vector::y() { return coordinates[1]; }
double& Vector::z() { return coordinates[2]; }

そして for ループ、イテレータ:

const double* Vector::begin() const { return coordinates; }
const double* Vector::end() const { return coordinates + 3; }

double* Vector::begin() { return coordinates; }
double* Vector::end() { return coordinates + 3; }

// (x, y, z) -> (x + 1, y + 1, z + 1)
for (auto& i : v) ++i;

ただし、ここにいる他の多くの人と同じように、私はあなたの質問の前提に同意しません。C++ の領域ではより自然な 0 ベースのインデックス付けを使用する必要があります。言語はすでに非常に複雑であり、将来コードを保守する人のためにさらに複雑にする必要はありません。

于 2012-08-08T00:02:08.297 に答える
0

真剣に、この 3 つの方法すべてをベンチマークしてください (つまり、減算と double[4] メソッドを、呼び出し元でゼロベースのインデックスを使用する方法と比較してください)。

一部のキャッシュ アーキテクチャで 16 バイト アラインメントを強制することで大きなメリットが得られる可能性は十分にあり、同様に、一部のコンパイラ/命令セット/コード パスの組み合わせで減算が実質的に無料になる可能性もあります。

確認する唯一の方法は、現実的なコードをベンチマークすることです。

于 2012-08-07T23:00:28.967 に答える