x + =はx = x + aよりも速いですか?


84

私はStroustrupの「C ++プログラミング言語」を読んでいました。彼は、変数に何かを追加する2つの方法のうちの1つだと言っています。

x = x + a;

そして

x += a;

彼は+=それがよりよく実行される可能性が高いので好む。彼はそれもより速く動くことを意味すると思います。
しかし、それは本当にですか?コンパイラなどに依存する場合、どうすれば確認できますか?


45
「C ++プログラミング言語」は1985年に最初に発行されました。最新バージョンは1997年に発行され、1997バージョンの特別版は2000年に発行されました。その結果、一部の部分が大幅に古くなっています。
JoeG 2012

5
2つの行は、まったく異なることを行う可能性があります。より具体的にする必要があります。
Kerrek SB 2012


26
最新のコンパイラは、これらの質問が「時代遅れ」と見なされるほど賢いです。
gd1 2012

2
重複した質問がC ++ではなくCについて尋ねているため、これを再度開きました。
Kev 2013

回答:


212

任意のコンパイラの価値は、その塩は、任意のビルトインタイプ(のための両方の構築のためにまったく同じ機械語列が生成されますintfloat長い文は本当にとして、単純なようであるようとして、など)x = x + a; と最適化が有効になっています。(特に、-O0デフォルトモードであるGCCは、デバッガーが常に変数値を見つけられるようにするために、完全に不要なストアをメモリに挿入するなどの最適化防止を実行します。)

ただし、ステートメントがより複雑な場合は、異なる可能性があります。fポインタを返す関数であるとすると、

*f() += a;

f一度だけ呼び出すのに対し、

*f() = *f() + a;

それを2回呼び出します。f副作用がある場合は、2つのうちの1つが間違っています(おそらく後者)。f副作用がない場合でも、コンパイラーは2番目の呼び出しを排除できない可能性があるため、後者は実際に遅くなる可能性があります。

また、ここではC ++について話しているので、オーバーロードするクラスタイプoperator+operator+=。場合はx、このようなA型であり、そして-最適化の前に-x += aに変換

x.operator+=(a);

一方x = x + a

auto TEMP(x.operator+(a));
x.operator=(TEMP);

さて、クラスが適切に書かれている場合、コンパイラのオプティマイザは、両方が同じ機械語を生成する羽目になり、十分に良いですが、それはビルトインタイプのためにあるように、それは確かなものではありません。これはおそらく、Stroustrupがの使用を奨励するときに考えていることです+=


14
別の側面もあります-読みやすさ。isに追加exprして逆に書くためのC ++イディオムvarvar+=expr、読者を混乱させます。
Tadeusz Kopec 2012

21
自分が書い*f() = *f() + a;ていることに気付いた場合は、実際に達成しようとしていることをよく調べたいと思うかもしれません...
Adam Davis

3
そして、var = var + exprがあなたを混乱させ、var + = exprが混乱させない場合、あなたは私が今まで出会った中で最も奇妙なソフトウェアエンジニアです。どちらも読み取り可能です。一貫性があることを確認してください(そして、私たち全員がop =を使用しているので、とにかくその
意味は

7
@PiotrDobrogost:質問に答えることの何が問題になっていますか?いずれにせよ、重複をチェックしたのは質問者でなければなりません。
Gorpik 2012

4
@PiotrDobrogostは、あなたが少し...嫉妬しているように私には思えます...重複を探し回る場合は、それを探してください。私は、たとえば、重複を探すよりも質問に答えることを好みます(以前に見たことがあることを特に思い出す質問でない限り)。それは時々より速くなることがあり、エルゴは質問をした人をより速く助けます。また、これはループではないことに注意してください。1は定数でaあり、揮発性、ユーザー定義型など、何でもかまいません。全然違う。実際、これがどのように閉鎖されたのか私にはわかりません。
Luchian Grigore 2012

56

同じになるdissasemblyを見ることで確認できます。

基本タイプの場合、どちらも同じように高速です。

これは、デバッグビルドによって生成された出力です(つまり、最適化は行われません)。

    a += x;
010813BC  mov         eax,dword ptr [a]  
010813BF  add         eax,dword ptr [x]  
010813C2  mov         dword ptr [a],eax  
    a = a + x;
010813C5  mov         eax,dword ptr [a]  
010813C8  add         eax,dword ptr [x]  
010813CB  mov         dword ptr [a],eax  

ユーザー定義型の場合は、あなたがオーバーロードすることができ、operator +そしてoperator +=、それは、それぞれの実装に依存します。


1
すべての場合に当てはまるわけではありません。(アトミックを使用せずに)メモリ位置を直接インクリメントするよりも、メモリアドレスをレジスタにロードしてインクリメントし、書き戻す方が高速であることがわかりました。私は...コードまで擦れる音を試してみましょう
ジェームズ・

ユーザー定義の型はどうですか?優れたコンパイラ同等のアセンブリ生成する必要がありますが、そのような保証はありません。
mfontanini 2012

1
@LuchianGrigoreいやが、もしa1でxあるvolatileコンパイラが生成することができますinc DWORD PTR [x]。これは遅いです。
ジェームズ

1
@Chiffaはコンパイラに依存しませんが、開発者に依存します。operator +何もせず、operator +=100000番目の素数を計算してから戻るように実装できます。もちろん、それは馬鹿げたことですが、それは可能です。
Luchian Grigore 2012

3
@James:あなたのプログラムは、性能差に敏感である場合++xtemp = x + 1; x = temp;、その後、おそらくそれはむしろCよりアセンブリで記述する必要があります++ ...
EmirCalabuch

11

はい!x副作用がある可能性がある場合の後者の場合、書くのも読むのも速く、理解するのも速くなります。したがって、人間にとっては全体的に高速です。一般に、人間の時間はコンピューターの時間よりもはるかにコストがかかるため、それがあなたが求めていたものであるに違いありません。正しい?


8

それは実際にはxとaのタイプと+の実装に依存します。ために

   T x, a;
   ....
   x = x + a;

コンパイラーは、x +の値を含む一時的なTを作成して評価する必要があり、それをxに割り当てることができます。(この操作中は、xまたはaをワークスペースとして使用できません)。

x + = aの場合、一時的なものは必要ありません。

些細なタイプの場合、違いはありません。


8

違いx = x + aとは、x += aマシンが通過しなければならない仕事の量です-いくつかのコンパイラは、(通常は行う)ことがあり、それは離れて最適化し、私たちはしばらくの間、最適化を無視した場合、通常は、何が起こることである元のコードスニペットでは、マシンは値をx2回ルックアップする必要がありますが、後者の場合、このルックアップは1回だけ実行する必要があります。

ただし、前述したように、今日、ほとんどのコンパイラは、命令を分析し、必要な結果のマシン命令を減らすのに十分なインテリジェント性を備えています。

PS:スタックオーバーフローの最初の答え!


6

このC ++にラベルを付けたので、投稿した2つのステートメントから知る方法はありません。「x」が何であるかを知る必要があります(答え「42」に少し似ています)。xがPODの場合、実際にはそれほど大きな違いはありません。ただし、xがクラスの場合operator +operator +=メソッドとメソッドにオーバーロードが発生し、動作が異なり、実行時間が非常に異なる可能性があります。


6

あなたが+=あなたがコンパイラの生活をずっと楽にしていると言うなら。コンパイラがそれx = x+aがと同じであることを認識するためx += aに、コンパイラは

  • 左側(x)を分析して、副作用がなく、常に同じl値を参照していることを確認します。たとえば、である可能性がありz[i]、両方が変更されないことを確認する必要がzありiます。

  • 右側(x+a)を分析し、それが合計であること、および左側がz[i] = a + *(z+2*0+i)。のように変換できたとしても、右側で1回だけ発生することを確認します。

に追加aすることを意味する場合x、コンパイラの作成者は、意味を言うだけでそれを高く評価します。そうすれば、コンパイラーのライターがすべてのバグを取り除くこと望んでいる部分を行使しているわけではなく、正直に頭を出せない限り、実際には作業が楽になることはありません。 Fortranモードの。


5

具体的な例として、単純な複素数型を想像してみてください。

struct complex {
    double x, y;
    complex(double _x, double _y) : x(_x), y(_y) { }
    complex& operator +=(const complex& b) {
        x += b.x;
        y += b.y;
        return *this;
    }
    complex operator +(const complex& b) {
        complex result(x+b.x, y+b.y);
        return result;
    }
    /* trivial assignment operator */
}

a = a + bの場合、追加の一時変数を作成してからコピーする必要があります。


これは非常に良い例であり、2つの演算子がどのように実装されたかを示しています。
Grijesh Chauhan 2013

5

あなたは間違った質問をしている。

これにより、アプリや機能のパフォーマンスが向上する可能性はほとんどありません。それがあったとしても、見つけるための方法は、することですプロファイルのコードを、それは確かにあなたをどのように影響するかを知っています。このレベルでどちらが速いかを心配するのではなく、明快さ、正確さ、読みやすさの観点から考えることがはるかに重要です。

これは、これが重要なパフォーマンス要因であっても、コンパイラーが時間の経過とともに進化することを考慮すると、特に当てはまります。誰かが新しい最適化を理解するかもしれません、そして今日の正しい答えは明日間違っているかもしれません。これは、時期尚早の最適化の典型的なケースです。

これは、パフォーマンスがまったく問題ではないということではありません...パフォーマンスの目標を達成するための間違ったアプローチであるというだけです。正しいアプローチは、プロファイリングツールを使用して、コードが実際に時間を費やしている場所、つまりどこに努力を集中するかを学習することです。


確かに、それは「いつそのような違いを考慮すべきか」という全体像ではなく、低レベルの質問として意図されていました。
シファ2013年

1
OPの質問は、他の回答(および賛成票)に示されているように、完全に正当なものでした。私たちはあなたのポイント(最初のプロファイルなど)を取得しても、間違いであるもののこの種のを知っているのは興味深い-あなたは本当にあなたが取る各決定の成果をプロファイリング、それぞれあなたが書くすべての些細な声明をプロファイリングするつもりですか?すでにケースを調査、プロファイリング、分解し、提供する一般的な回答を持っているSOの人々がいる場合でも?

4

それはマシンとそのアーキテクチャに依存するはずだと思います。そのアーキテクチャで間接メモリアドレス指定が許可されている場合、コンパイラの作成者は代わりに次のコードを使用する可能性があります(最適化のため)。

mov $[y],$ACC

iadd $ACC, $[i] ; i += y. WHICH MIGHT ALSO STORE IT INTO "i"

一方、i = i + y(最適化なしで)に翻訳される可能性があります:

mov $[i],$ACC

mov $[y],$B 

iadd $ACC,$B

mov $B,[i]


とは言うものの、iポインタを返す関数であるかどうかなど、他の複雑さも考慮する必要があります。GCCを含むほとんどの実動レベルのコンパイラーは、両方のステートメントに対して同じコードを生成します(整数の場合)。


2

いいえ、どちらの方法も同じように扱われます。


10
オーバーロードされた演算子を持つユーザー定義型の場合は違います。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.