私はいくつかの言語で次のことを知っています:
a += b
次のものよりも効率的です。
a = a + b
一時変数を作成する必要がなくなるためです。これはCの場合ですか?+= を使用する方が効率的ですか(そして、したがってまた-=
*=
など)
私はいくつかの言語で次のことを知っています:
a += b
次のものよりも効率的です。
a = a + b
一時変数を作成する必要がなくなるためです。これはCの場合ですか?+= を使用する方が効率的ですか(そして、したがってまた-=
*=
など)
だからここに決定的な答えがあります...
$ cat junk1.c
#include <stdio.h>
int main()
{
long a, s = 0;
for (a = 0; a < 1000000000; a++)
{
s = s + a * a;
}
printf("Final sum: %ld\n", s);
}
michael@isolde:~/junk$ cat junk2.c
#include <stdio.h>
int main()
{
long a, s = 0;
for (a = 0; a < 1000000000; a++)
{
s += a * a;
}
printf("Final sum: %ld\n", s);
}
michael@isolde:~/junk$ for a in *.c ; do gcc -O3 -o ${a%.c} $a ; done
michael@isolde:~/junk$ time ./junk1
Final sum: 3338615082255021824
real 0m2.188s
user 0m2.120s
sys 0m0.000s
michael@isolde:~/junk$ time ./junk2
Final sum: 3338615082255021824
real 0m2.179s
user 0m2.120s
sys 0m0.000s
...私のコンピュータと私のオペレーティングシステムで実行されているコンパイラの場合。結果は変わる場合と変わらない場合があります。ただし、私のシステムでは、時間は同じです。ユーザー時間は2.120秒です。
ここで、最新のコンパイラがどれほど印象的であるかを示すためa * a
に、割り当てで式を使用したことに注意してください。これは、この小さな問題が原因です。
$ cat junk.c
#include <stdio.h>
int main()
{
long a, s = 0;
for (a = 0; a < 1000000000; a++)
{
s = s + a;
}
printf("Final sum: %ld\n", s);
}
michael@isolde:~/junk$ gcc -O3 -S junk.c
michael@isolde:~/junk$ cat junk.s
.file "junk.c"
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "Final sum: %ld\n"
.text
.p2align 4,,15
.globl main
.type main, @function
main:
.LFB22:
.cfi_startproc
movabsq $499999999500000000, %rdx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
jmp __printf_chk
.cfi_endproc
.LFE22:
.size main, .-main
.ident "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
.section .note.GNU-stack,"",@progbits
コンパイラーは私のループを理解し、累積合計を計算するまで展開し、それを定数として埋め込んで、あらゆる種類のループ構造を完全にスキップして出力しました。s = s + a
賢いオプティマイザーに直面して、とを区別する上で意味のあるエッジを見つけることが本当にできると思いますs += a
か?!
これは実際にはコンパイラ固有の質問ですが、最新のコンパイラはすべて同じ結果になると思います。Visual Studio 2008 の使用:
int main() {
int a = 10;
int b = 30;
a = a + b;
int c = 10;
int d = 50;
c += d;
}
行 a = a + b は分解されています
0014139C mov eax,dword ptr [a]
0014139F add eax,dword ptr [b]
001413A2 mov dword ptr [a],eax
行 c += d には逆アセンブルがあります
001413B3 mov eax,dword ptr [c]
001413B6 add eax,dword ptr [d]
001413B9 mov dword ptr [c],eax
これは同じです。それらは同じコードにコンパイルされます。
それは何であるかに依存しa
ます。
a += b
in C は定義上 と同等ですがa = a + b
、抽象的な観点からa
前者のバリアントでは が 1 回だけ評価される点が異なります。が「純粋な」値である場合a
、つまり、一度評価しても何度も評価してもプログラムの動作に影響がない場合は、効率を含むすべての点でa += b
と厳密に同等です。a = a + b
言い換えれば、実際に と の間a += b
で自由に選択できる状況ではa = a + b
(つまり、それらが同じことを行うことを知っていることを意味します)、それらは一般的にまったく同じ効率を持ちます。一部のコンパイラa
では、関数呼び出しを表すときに問題が発生する可能性があります (一例として、おそらく意図したものではありません) が、a
不揮発性変数である場合、両方の式に対して生成されるマシン コードは同じになります。
別の例として、a
が volatile 変数の場合、a += b
とa = a + b
の動作が異なるため、効率も異なります。ただし、それらは同等ではないため、そのような場合にはあなたの質問は当てはまりません。
質問に示されている単純なケースでは、大きな違いはありません。次のような式がある場合、代入演算子のスコアはどこにありますか。
s[i]->m[j1].k = s[i]->m[jl].k + 23; // Was that a typo?
対:
s[i]->m[j1].k += 23;
2 つの利点 - タイピングの減少は数えていません。1 番目と 2 番目の式が異なる場合、タイプミスがあったかどうかは疑問の余地がありません。コンパイラは複雑な式を 2 回評価しません。最近ではそれほど大きな違いはない可能性があります (コンパイラの最適化は以前よりもはるかに優れています) が、さらに複雑な式を持つことができます (別の翻訳単位で定義された関数を評価するなど)。式を 2 回評価することをコンパイラが回避できない場合があります。
s[i]->m[somefunc(j1)].k = s[i]->m[somefunc(j1)].k + 23;
s[i]->m[somefunc(j1)].k += 23;
また、次のように書くこともできます (勇気がある場合):
s[i++]->m[j1++].k += 23;
しかし、次のように書くことはできません:
s[i++]->m[j1++].k = s[i]->m[j1].k + 23;
s[i]->m[j1].k = s[i++]->m[j1++].k + 23;
(またはその他の順列)評価の順序が定義されていないためです。
a += b
よりも効率的です
a = a + b
前者は 6 回のキーストロークを必要とし、後者は 9 回のキーストロークを必要とするためです。
最新のハードウェアでは、たとえコンパイラが愚かで、どちらか一方に遅いコードを使用していたとしても、プログラムの存続期間中に節約される合計時間は、余分な 3 つのキー ストロークを入力するのにかかる時間よりも短い可能性があります。
ただし、他の人が言ったように、コンパイラはほぼ確実にまったく同じコードを生成するため、前者の方が効率的です。
読みやすさを考慮したとしても、ほとんどの C プログラマーはおそらく前者を後者よりも頭の中ですばやく解析します。これは非常に一般的なパターンだからです。
a
とb
が通常の変数であり、両者が同等の結果を生成する限り、真に古いまたは無能に書かれたコンパイラを除いて、違いはないはずです。
C ではなく C++ を扱っている場合は、演算子のオーバーロードにより、より大きな違いが生じる可能性があります。
ほとんどすべての場合、この 2 つは同じ結果を生成します。