Stroustrupの「The C++ Programming Language」を読んでいたところ、変数に何かを追加する2つの方法のうち、
x = x + a;
そして
x += a;
彼は+=
実装がより適切である可能性が高いためです。彼はそれがより速く動作することを意味すると思います。
しかし、本当にそうですか?コンパイラなどに依存している場合、どのように確認すればよいですか?
ソルトに値するコンパイラーは、ステートメントが本当に単純である限り、組み込み型(int
、float
など)の両方の構成体に対してまったく同じ機械語シーケンスを生成します。 x = x + a;
および最適化が有効になっている。 (特に、GCCの-O0
(デフォルトモード)は、デバッガーが常に変数値を検出できるようにするために、メモリに完全に不要なストアを挿入するなど、anti-optimizationsを実行します。
ただし、ステートメントがより複雑な場合は、異なる場合があります。 f
がポインターを返す関数であるとします。
*f() += a;
f
を1回だけ呼び出しますが、
*f() = *f() + a;
2回呼び出します。 f
に副作用がある場合、2つのうちの1つが間違っています(おそらく後者)。 f
に副作用がない場合でも、コンパイラは2番目の呼び出しを排除できない可能性があるため、後者の方が実際に遅くなる可能性があります。
ここでC++について説明しているため、operator+
とoperator+=
をオーバーロードするクラス型では状況がまったく異なります。 x
がそのようなタイプの場合、最適化の前にx += a
は
x.operator+=(a);
一方、x = x + a
は
auto TEMP(x.operator+(a));
x.operator=(TEMP);
現在、クラスが適切に記述されている場合andコンパイラーのオプティマイザーは十分であり、両方とも同じ機械語を生成しますが、組み込み型の場合のように確実なことではありません。これはおそらく、Stroustrupが+=
の使用を推奨するときに考えていることです。
分解を見て確認できますが、これは同じです。
基本型の場合、両方とも同等に高速です。
これは、デバッグビルドによって生成された出力です(つまり、最適化なし):
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 +=
、それはそれぞれの実装に依存します。
はい! x
に副作用がある場合の後者については、書くのが速く、読むのが速く、理解するのが速いです。したがって、人間にとっては全体的に高速です。一般に、人間の時間はコンピューターの時間よりもはるかに高価なので、それはあなたが尋ねていたものでなければなりません。正しい?
x = x + a
とx += a
の違いは、マシンが処理しなければならない作業量です。一部のコンパイラーは、最適化を無効にすることがあります(通常は実行します)が、通常、しばらくの間最適化を無視すると、前者のコードスニペットでは、マシンはx
の値を2回ルックアップする必要がありますが、後者では、このルックアップは1回だけ実行する必要があります。
しかし、私が述べたように、今日のほとんどのコンパイラーは、命令を分析し、結果として必要なマシン命令を減らすのに十分なほどインテリジェントです。
PS:スタックオーバーフローの最初の答え!
それは本当にxとaの型と+の実装に依存します。にとって
T x, a;
....
x = x + a;
コンパイラは、評価中にx + aの値を含む一時的なTを作成する必要があり、それをxに割り当てることができます。 (この操作中にワークスペースとしてxまたはaを使用することはできません)。
X + = aの場合、一時的なものは必要ありません。
些細なタイプの場合、違いはありません。
このC++にラベルを付けたので、投稿した2つのステートメントから知る方法はありません。 「x」が何であるかを知る必要があります(答え「42」に少し似ています)。 x
がPODである場合、実際には大きな違いはありません。ただし、x
がクラスの場合、operator +
メソッドとoperator +=
メソッドのオーバーロードが存在する可能性があり、動作が異なると実行時間が非常に異なる可能性があります。
間違った質問をしている。
これにより、アプリまたは機能のパフォーマンスが向上することはほとんどありません。それがあったとしても、それを見つける方法はprofileコードであり、それが確実にあなたにどのように影響するかを知ることです。どちらが速いかをこのレベルで心配する代わりに、明快さ、正確さ、読みやすさの観点から考えることははるかに重要です。
これは、これが重要なパフォーマンス要因であっても、コンパイラーが時間とともに進化することを考慮すると特に当てはまります。誰かが新しい最適化を考え出すかもしれず、今日の正しい答えは明日間違っている可能性があります。これは、時期尚早な最適化の典型的なケースです。
これは、パフォーマンスがまったく問題ではないということではありません...パフォーマンス目標を達成するための間違ったアプローチだというだけです。正しいアプローチは、プロファイリングツールを使用して、コードが実際にどこで時間を費やしているか、したがってどこに努力を集中させるかを知ることです。
_+=
_と言うと、コンパイラの生活がずっと楽になります。コンパイラが_x = x+a
_が_x += a
_と同じであることを認識するために、コンパイラは
左側(x
)を分析して、副作用がなく、常に同じl値を参照していることを確認します。たとえば、_z[i]
_であり、z
とi
の両方が変更されないようにする必要があります。
右側を分析し(_x+a
_)、それが総和であること、およびz[i] = a + *(z+2*0+i)
。
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の場合、追加の一時変数を作成してからコピーする必要があります。
いいえ、どちらの方法でも同じように処理されます。