私はStroustrupの「C ++プログラミング言語」を読んでいました。彼は、変数に何かを追加する2つの方法のうちの1つだと言っています。
x = x + a;
そして
x += a;
彼は+=
それがよりよく実行される可能性が高いので好む。彼はそれもより速く動くことを意味すると思います。
しかし、それは本当にですか?コンパイラなどに依存する場合、どうすれば確認できますか?
私はStroustrupの「C ++プログラミング言語」を読んでいました。彼は、変数に何かを追加する2つの方法のうちの1つだと言っています。
x = x + a;
そして
x += a;
彼は+=
それがよりよく実行される可能性が高いので好む。彼はそれもより速く動くことを意味すると思います。
しかし、それは本当にですか?コンパイラなどに依存する場合、どうすれば確認できますか?
回答:
任意のコンパイラの価値は、その塩は、任意のビルトインタイプ(のための両方の構築のためにまったく同じ機械語列が生成されますint
、float
長い文は本当にとして、単純なようであるようとして、など)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がの使用を奨励するときに考えていることです+=
。
expr
して逆に書くためのC ++イディオムvar
はvar+=expr
、読者を混乱させます。
*f() = *f() + a;
ていることに気付いた場合は、実際に達成しようとしていることをよく調べたいと思うかもしれません...
1
は定数でa
あり、揮発性、ユーザー定義型など、何でもかまいません。全然違う。実際、これがどのように閉鎖されたのか私にはわかりません。
同じになる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 +=
、それは、それぞれの実装に依存します。
a
1でx
あるvolatile
コンパイラが生成することができますinc DWORD PTR [x]
。これは遅いです。
operator +
何もせず、operator +=
100000番目の素数を計算してから戻るように実装できます。もちろん、それは馬鹿げたことですが、それは可能です。
++x
とtemp = x + 1; x = temp;
、その後、おそらくそれはむしろCよりアセンブリで記述する必要があります++ ...
違いx = x + a
とは、x += a
マシンが通過しなければならない仕事の量です-いくつかのコンパイラは、(通常は行う)ことがあり、それは離れて最適化し、私たちはしばらくの間、最適化を無視した場合、通常は、何が起こることである元のコードスニペットでは、マシンは値をx
2回ルックアップする必要がありますが、後者の場合、このルックアップは1回だけ実行する必要があります。
ただし、前述したように、今日、ほとんどのコンパイラは、命令を分析し、必要な結果のマシン命令を減らすのに十分なインテリジェント性を備えています。
PS:スタックオーバーフローの最初の答え!
あなたが+=
あなたがコンパイラの生活をずっと楽にしていると言うなら。コンパイラがそれx = x+a
がと同じであることを認識するためx += a
に、コンパイラは
左側(x
)を分析して、副作用がなく、常に同じl値を参照していることを確認します。たとえば、である可能性がありz[i]
、両方が変更されないことを確認する必要がz
ありi
ます。
右側(x+a
)を分析し、それが合計であること、および左側がz[i] = a + *(z+2*0+i)
。のように変換できたとしても、右側で1回だけ発生することを確認します。
に追加a
することを意味する場合x
、コンパイラの作成者は、意味を言うだけでそれを高く評価します。そうすれば、コンパイラーのライターがすべてのバグを取り除くことを望んでいる部分を行使しているわけではなく、正直に頭を出せない限り、実際には作業が楽になることはありません。 Fortranモードの。
具体的な例として、単純な複素数型を想像してみてください。
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の場合、追加の一時変数を作成してからコピーする必要があります。
あなたは間違った質問をしている。
これにより、アプリや機能のパフォーマンスが向上する可能性はほとんどありません。それがあったとしても、見つけるための方法は、することですプロファイルのコードを、それは確かにあなたをどのように影響するかを知っています。このレベルでどちらが速いかを心配するのではなく、明快さ、正確さ、読みやすさの観点から考えることがはるかに重要です。
これは、これが重要なパフォーマンス要因であっても、コンパイラーが時間の経過とともに進化することを考慮すると、特に当てはまります。誰かが新しい最適化を理解するかもしれません、そして今日の正しい答えは明日間違っているかもしれません。これは、時期尚早の最適化の典型的なケースです。
これは、パフォーマンスがまったく問題ではないということではありません...パフォーマンスの目標を達成するための間違ったアプローチであるというだけです。正しいアプローチは、プロファイリングツールを使用して、コードが実際に時間を費やしている場所、つまりどこに努力を集中するかを学習することです。
それはマシンとそのアーキテクチャに依存するはずだと思います。そのアーキテクチャで間接メモリアドレス指定が許可されている場合、コンパイラの作成者は代わりに次のコードを使用する可能性があります(最適化のため)。
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を含むほとんどの実動レベルのコンパイラーは、両方のステートメントに対して同じコードを生成します(整数の場合)。