3

2 つの構造体がある場合:

struct A
{
    float x, y;
    inline A operator*(A b) 
    {
        A out;
        out.x = x * b.x;
        out.y = y * b.y;
        return out;
    } 
}

そして同等の構造体

struct B
{
    float x, y;
}

inline B operator*(B a, B b) 
{
    B out;
    out.x = a.x * b.x;
    out.y = a.y * b.y;
    return out;
} 

B の operator* が別の方法でコンパイルしたり、A の operator* よりも遅くまたは速く実行されたりする理由を知っていますか (関数内で行われる実際のアクションは無関係である必要があります)。

つまり、インライン演算子をメンバーとして宣言するのではなく、メンバーとして宣言すると、実際の関数の速度に一般的な影響が及ぶのでしょうか?

現在、インライン メンバ オペレータ スタイルに従っているさまざまな構造体がいくつかありますが、代わりに有効な C コードになるように変更したいと考えていました。その前に、パフォーマンス/コンパイルに変更があるかどうかを知りたいと思いました。

4

3 に答える 3

11

B::operator*あなたが書いた方法では、少し遅く実行されると思います。これは、 の「内部」実装A::operator*が次のようになっているためです。

inline A A::operator*(A* this, A b) 
{ 
    A out;
    out.x = this->x * b.x;
    out.y = this->y * b.y;
    return out;
}

そのAため、左側の引数へのポインターを関数に渡しますが、関数をB呼び出す前にそのパラメーターのコピーを作成する必要があります。どちらも右側のパラメーターのコピーを作成する必要があります。

参照を使用して記述し、それを修正した場合、コードははるかに優れたものになり、おそらくAandに対して同じものを実装するでしょう。Bconst

struct A
{
    float x, y;
    inline A operator*(const A& b) const 
    {
        A out;
        out.x = x * b.x;
        out.y = y * b.y;
        return out;
    } 
}

struct B
{
    float x, y;
}

inline B operator*(const B& a, const B& b) 
{
    B out;
    out.x = a.x * b.x;
    out.y = a.y * b.y;
    return out;
}

結果は事実上一時的なものであるため、参照ではなくオブジェクトを返す必要があります (変更された既存のオブジェクトを返すわけではありません)。


補遺

ただし、両方の引数の const 参照渡しを使用すると、B では、逆参照により、A よりも効果的に高速になりますか?

まず、すべてのコードを綴ると、どちらも同じ逆参照を伴います。( のメンバーへのアクセスはthis、ポインターの逆参照を意味することに注意してください。)

ただし、その場合でも、コンパイラがどれほど賢いかによって異なります。この場合、構造体を調べて、2 つのフロートであるためレジスタに格納できないと判断したとします。したがって、ポインタを使用してそれらにアクセスします。したがって、逆参照されたポインターのケース (参照が実装されるもの) が最善です。アセンブリは次のようになります (これは疑似アセンブリ コードです)。

// Setup for the function. Usually already done by the inlining.
r1 <- this
r2 <- &result
r3 <- &b

// Actual function.
r4 <- r1[0]
r4 <- r4 * r3[0]
r2[0] <- r4
r4 <- r1[4]
r4 <- r4 * r3[4]
r2[4] <- r4

これは、RISC のようなアーキテクチャ (ARM など) を想定しています。x86 はおそらく少ないステップを使用しますが、とにかく命令デコーダーによってこのレベルの詳細に拡張されます。ポイントは、レジスター内のポインターのすべての固定オフセット逆参照であり、それが得られるのとほぼ同じ速度であるということです。オプティマイザーは、より賢く、複数のレジスターに渡ってオブジェクトを実装しようとすることができますが、その種のオプティマイザーを作成するのは非常に困難です。result(保存されていない単なる一時的なオブジェクトであれば、LLVM タイプのコンパイラ/オプティマイザがその最適化を簡単に実行できるのではないかと、密かに疑っていますが。)

したがって、を使用しthisているため、暗黙的なポインター逆参照があります。しかし、オブジェクトがスタック上にある場合はどうなるでしょうか? 役に立ちません。スタック変数は、スタック ポインター (使用されている場合はフレーム ポインター) の固定オフセット逆参照になります。したがって、コンパイラがオブジェクトを取得して複数のレジスタに分散するのに十分なほど明るくない限り、最終的にどこかでポインターを逆参照しています。

-Sオプションを自由に渡してgcc、最終コードの逆アセンブリを取得して、実際に何が起こっているかを確認してください。

于 2012-05-20T03:28:53.220 に答える
3

inline-ing はコンパイラに任せるべきです。

とはいえ、クラス定義内で定義された関数 ( の場合と同様A) はinlineデフォルトです。inline指定子 forはA::operator *役に立ちません。

より興味深いケースは、クラス定義の外にメンバー関数定義がある場合です。ここで、これが頻繁に使用され、呼び出し元内で命令をインラインでコンパイルする必要があるというヒントをコンパイラーに提供したい場合 (コンパイラーはこれを無視する可能性があります)、 inline が必要です。

C++ FAQ 9を読んでください。

于 2012-05-20T03:31:23.193 に答える
2

構造体の書き方は次のとおりです。

struct A
{
    float x, y;
    A(float ax, float ay) : x(ax), y(ay) { }
    A operator*(const A& b) const { return b(x * b.x, y * b.y); } 
}

質問に答えるために、はい、メンバー関数として演算子を書くことは、特定の状況ではわずかに速くなる可能性がありますが、コードに顕著な違いをもたらすほどではありません.

いくつかのメモ:

  1. inline キーワードの使用について心配する必要はありません。最適化コンパイラは、何をインライン化し、何をインライン化しないかを独自に決定します。

  2. 初期化コンストラクターを使用します。コードの可読性が向上するためです。小さなパフォーマンス上のメリットをもたらす可能性があることを知って、よりよく眠りましょう。

  3. できるだけ頻繁に const 参照によって構造体を渡します。

  4. 速くないスタイルの良いコードを書くことに集中してください。ほとんどのコードは十分に高速ですが、そうでない場合は、おそらくアルゴリズムまたは IO の処理に骨の折れる何かが原因です。

于 2012-05-20T03:46:05.967 に答える