7

いくつかの数学型を実装しています。演算子を最適化して、作成、破棄、およびコピーされるメモリの量を最小限に抑えたいと考えています。デモンストレーションとして、Quaternion の実装の一部をお見せします。

class Quaternion
{
public:
    double w,x,y,z;

    ...

    Quaternion  operator+(const Quaternion &other) const;
}

次の2つの実装が互いにどのように異なるかを知りたいです。メモリが作成されない場所にインプレースで動作する += 実装がありますが、クォータニオンを使用するいくつかの高レベルの操作では、+= ではなく + を使用すると便利です。

__forceinline Quaternion Quaternion::operator+( const Quaternion &other ) const
{
    return Quaternion(w+other.w,x+other.x,y+other.y,z+other.z);
}

__forceinline Quaternion Quaternion::operator+( const Quaternion &other ) const
{
    Quaternion q(w+other.w,x+other.x,y+other.y,z+other.z);
    return q;
}

私の C++ は完全に独学なので、いくつかの最適化に関しては、コンパイラがこれらのことをどのように処理するかを正確に知らないため、何をすべきかわかりません。また、これらのメカニズムはどのように非インライン実装に変換されますか。

私のコードに対するその他の批判は大歓迎です。

4

4 に答える 4

10

最初の例では、コンパイラが「戻り値の最適化」(RVO) と呼ばれるものを使用できる可能性があります。

2 番目の例では、コンパイラが "名前付き戻り値の最適化" (NRVO) と呼ばれるものを潜在的に使用できるようにします。これら 2 つの最適化は、明らかに密接に関連しています。

Microsoft による NRVO の実装の詳細については、次の場所を参照してください。

この記事では、NRVO サポートが VS 2005 (MSVC 8.0) で開始されたことを示していることに注意してください。同じことが RVO に適用されるかどうかは具体的に述べられていませんが、MSVC はバージョン 8.0 より前に RVO 最適化を使用していたと思います。

Andrei Alexandrescu による Move Constructors に関するこの記事には、RVO がどのように機能するか (および、コンパイラがそれを使用しない場合と理由) についての良い情報があります。

このビットを含む:

各コンパイラ、多くの場合、各コンパイラ バージョンには、RVO を検出して適用するための独自の規則があると聞いてがっかりするでしょう。名前のない一時値を返す関数にのみ RVO を適用するものもあります (RVO の最も単純な形式)。より洗練されたものは、関数が返す名前付きの結果がある場合にも RVO を適用します (いわゆる名前付き RVO、または NRVO)。

本質的に、コードを書くとき、コードをどのように正確に書くか (「正確に」という非常に流動的な定義の下で)、月の満ち欠け、および靴のサイズに応じて、RVO がコードに移植可能に適用されることを期待できます。 .

この記事は 2003 年に書かれたもので、コンパイラは今では大幅に改善されているはずです。うまくいけば、コンパイラが RVO/NRVO をいつ使用するかについて、月の満ち欠けがあまり重要ではなくなります (おそらく、曜日に関係します)。上記のように、MS は 2005 年まで NRVO を実装していなかったようです。おそらく、Microsoft でコンパイラに取り組んでいる誰かが、以前よりも半分サイズ大きい、より快適な新しい靴を手に入れたときです。

あなたの例は十分に単純なので、どちらも最新のコンパイラ バージョンで同等のコードを生成すると予想されます。

于 2009-08-21T20:18:59.513 に答える
6

あなたが提示した2つの実装の間に、実際には違いはありません。あらゆる種類の最適化を行うコンパイラは、ローカル変数を最適化します。

+= 演算子に関しては、クォータニオンを不変オブジェクトにするかどうかについて、もう少し複雑な議論が必要になる可能性があります...私は常に、このようなオブジェクトを不変オブジェクトとして作成する方向に導きます。(しかし、繰り返しになりますが、私は管理されたコーダーでもあります)

于 2009-08-21T19:16:03.103 に答える
2

現在のコンセンサスは、新しいオブジェクトを作成しないすべての ?= 演算子を最初に実装する必要があるということです。例外の安全性が問題であるか(あなたの場合はおそらくそうではない)、または目標であるかに応じて、 ?= 演算子の定義は異なる場合があります。その後、オペレーターを実装しますか?値渡しのセマンティクスを使用する ?= 演算子に関して自由な関数として。

// thread safety is not a problem
class Q
{
   double w,x,y,z;
public:
   // constructors, other operators, other methods... omitted
   Q& operator+=( Q const & rhs ) {
      w += rhs.w;
      x += rhs.x;
      y += rhs.y;
      z += rhs.z;
      return *this;
   }
};
Q operator+( Q lhs, Q const & rhs ) {
   lhs += rhs;
   return lhs;
}

これには次の利点があります。

  • ロジックの実装は 1 つだけです。クラスが変更された場合、operator?= と operator? を再実装するだけで済みます。自動的に適応します。
  • フリー関数演算子は、コンパイラの暗黙的な変換に関して対称的です
  • 演算子の最も効率的な実装ですか? コピーに関して見つけることができます

オペレーターの効率?

オペレーターに電話したら?2 つの要素では、3 番目のオブジェクトを作成して返す必要があります。上記のアプローチを使用すると、コピーはメソッド呼び出しで実行されます。そのままでは、一時オブジェクトを渡すときに、コンパイラはコピーを省略できます。これは、「コンパイラーはコピーを除外できる」とではなく、「コンパイラーはコピーを除外できることを認識している」と解釈する必要があることに注意してください。走行距離はコンパイラーによって異なり、同じコンパイラーでもコンパイルの実行ごとに異なる結果が得られる可能性があります (オプティマイザーで使用できるパラメーターまたはリソースが異なるため)。

a次のコードでは、との合計で一時が作成され、最終結果で 2 番目の一時を作成するには、その一時を と一緒にb再度渡す必要があります。operator+c

Q a, b, c;
// initialize values
Q d = a + b + c;

operator+渡しのセマンティクスがある場合、コンパイラは値渡しのコピーを省略できます (コンパイラは、2 回目の呼び出しの直後に一時ファイルが破棄されることを認識しており、operator+渡すために別のコピーを作成する必要はありません)。

コード内でoperator?1 行の関数 ( ) として実装できたとしても、そうすべきではありません。Q operator+( Q lhs, Q const & rhs ) { return lhs+=rhs; }その理由は、によって返されoperator?=た参照が実際に同じオブジェクトへの参照であるかどうかをコンパイラが認識できないためです。return ステートメントがlhsオブジェクトを明示的に受け取るようにすることで、コンパイラーは return コピーを省略できることを認識します。

型に関する対称性

Ttype から typeへの暗黙的な変換があり、各型のQ2 つのインスタンスtおよびqそれぞれがある場合、(t+q)および(q+t)両方が呼び出し可能であることが期待されます。operator+内部でメンバ関数として実装するQと、コンパイラはtオブジェクトを一時Qオブジェクトに変換できず、後で(Q(t)+q)メンバ関数を呼び出すために左側で型変換を実行できないため、呼び出すことができません。したがって、メンバー関数の実装でt+qはコンパイルされません。

これは、算術用語で対称でない演算子にも当てはまることに注意してください。型について話しているのです。を aに昇格させることで aTから aを差し引くことができる場合、別の自動昇格でaから aを差し引くことができない理由はありません。QTQQT

于 2009-08-21T22:51:36.380 に答える
2

最適化がオンになっているときにこれら 2 つの実装がまったく同じアセンブリ コードを生成しない場合は、別のコンパイラの使用を検討する必要があります。:)そして、関数がインライン化されているかどうかは問題ではないと思います。

ところで、__forceinline非常に移植性がないことに注意してください。普通の古い標準inlineを使用して、コンパイラーに決定させます。

于 2009-08-21T19:24:30.870 に答える