回答:
エグゼクティブサマリー:いいえ。
i++
++i
の古い値i
を後で使用するために保存する必要があるため、はよりも遅くなる可能性がありますが、実際には、最近のすべてのコンパイラはこれを最適化します。
我々は両方で、この関数のコードを見て、これを実証することができます++i
し、i++
。
$ cat i++.c
extern void g(int i);
void f()
{
int i;
for (i = 0; i < 100; i++)
g(i);
}
++i
およびを除いて、ファイルは同じですi++
。
$ diff i++.c ++i.c
6c6
< for (i = 0; i < 100; i++)
---
> for (i = 0; i < 100; ++i)
それらをコンパイルし、生成されたアセンブラーも取得します。
$ gcc -c i++.c ++i.c
$ gcc -S i++.c ++i.c
そして、生成されたオブジェクトとアセンブラファイルの両方が同じであることがわかります。
$ md5 i++.s ++i.s
MD5 (i++.s) = 90f620dda862cd0205cd5db1f2c8c06e
MD5 (++i.s) = 90f620dda862cd0205cd5db1f2c8c06e
$ md5 *.o
MD5 (++i.o) = dd3ef1408d3a9e4287facccec53f7d22
MD5 (i++.o) = dd3ef1408d3a9e4287facccec53f7d22
++i
代わりに使用することをお勧めしますi++
。絶対にしない理由はありません。ソフトウェアがそれを最適化しないツールチェーンを通過する場合、ソフトウェアはより効率的になります。それが入力するだけの簡単なようで考えると++i
、それは型にあるとしてi++
、使用されないように言い訳は本当にありません++i
最初の場所では。
Andrew Koenigによる効率と意図から:
第1に、少なくとも整数変数が関係している
++i
場合i++
、がより効率的であることは明らかではありません。
そして:
したがって、質問する必要があるのは、これら2つの操作のどちらが速いかではなく、これら2つの操作のどちらが、達成しようとしていることをより正確に表現しているかということです。式の値を使用していない場合、変数の値をコピーし、変数をインクリメントしてからコピーを破棄する理由がないため、の
i++
代わりに使用++i
する理由はありません。
したがって、結果の値が使用されない場合は、を使用します++i
。しかし、それがより効率的だからではありません。それは、私の意図を正しく述べているからです。
i++
と同じ方法で、つまりターゲット動詞オブジェクトの形式で、ターゲットオペランドを動詞演算子の左側にコーディングします。の場合、正しいオブジェクトはありませんが、ルールは適用され、ターゲットは動詞演算子の左側に配置されます。i += n
i = i + n
i++
より良い答えは、++i
時々速くなりますが遅くなることはありません。
誰もi
がそれをなどの通常の組み込み型であると想定しているようint
です。この場合、測定可能な違いはありません。
ただし、i
複合型の場合、測定可能な差が見つかる可能性があります。i++
あなたがそれをインクリメントする前に、あなたのクラスのコピーを作成する必要があります。コピーに何が関係しているかによっては++it
、最終的な値を返すだけなので、実際には遅くなる可能性があります。
Foo Foo::operator++()
{
Foo oldFoo = *this; // copy existing value - could be slow
// yadda yadda, do increment
return oldFoo;
}
別の違いは++i
、値ではなく参照を返すオプションがあることです。繰り返しますが、オブジェクトのコピーの作成に関係するものによっては、これが遅くなる可能性があります。
これが発生する可能性のある実際の例は、イテレータの使用です。イテレータのコピーがアプリケーションのボトルネックになることはほとんどありませんが、結果が影響を受けない場所では++i
なく、使用する習慣を身につけることは依然として良い習慣ですi++
。
簡潔な答え:
間に違いはあり決してありませんi++
し、++i
速度の点では。優れたコンパイラは、2つのケースで異なるコードを生成するべきではありません。
長い答え:
どのような他のすべての答えが言及に失敗したこととの違いということである++i
対がi++
唯一それが発見された式の中で理にかなっています。
以下の場合にはfor(i=0; i<n; i++)
、i++
独自の表現で一人である:前のシーケンスポイントがありi++
、それの後に1があります。このように生成された唯一のマシンコードは、「増加であるi
ことで1
、それは、このプログラムの残りの部分に関連して配列決定する方法を明確に定義されています」。あなたは接頭辞に変更するかどうので++
、それは問題ではわずかで、あなたはまだ、単に「増加マシンコードになるだろうしませんi
で1
」。
間の違い++i
とi++
のような表現で唯一の事項array[i++] = x;
に対してarray[++i] = x;
。i
常駐するレジスタを後で再ロードする必要があるため、そのような操作ではpostfixが遅くなると主張して言う人もいます。ただし、C標準で呼ばれている「抽象マシンの動作を壊さない」限り、コンパイラは自由に命令を自由に順序付けできることに注意してください。
したがって、次のarray[i++] = x;
ようにマシンコードに変換されると想定する場合があります。
i
レジスタAに格納します。i
レジスタAに値を格納します。//ここに追加の命令があるため、これはすでに1回実行したためです。i
ます。コンパイラは、次のようなコードをより効率的に生成することもできます。
i
レジスタAに格納します。i
ます。Cプログラマとして、postfix ++
が最後に起こると考えるように訓練されているからといって、マシンコードをそのように順序付ける必要はありません。
したがって++
、Cの接頭辞と接尾辞の間に違いはありません。Cプログラマとしてあなたが変える必要があるのは、理由のない根拠なしに、場合によっては接頭辞を使用し、他の場合には接尾辞を一貫して使用しない人です。これは、Cがどのように機能するかについて不確実であること、または言語についての誤った知識を持っていることを示唆しています。これは常に悪い兆候であり、迷信または「宗教的教義」に基づいて、プログラムで他の疑わしい決定を行っていることを示しています。
「プレフィックス++
は常に速い」は、Cプログラマになる可能性の高いプログラマーの間でよくある偽の教義です。
スコット・マイヤーズ、より効果的なc ++の 項目6からリーフを取得:増分演算と減分演算のプレフィックス形式とポストフィックス形式を区別します。
オブジェクトに関しては、特にイテレータに関しては、接頭辞バージョンが常に接尾辞よりも優先されます。
演算子の呼び出しパターンを見ると、この理由があります。
// Prefix
Integer& Integer::operator++()
{
*this += 1;
return *this;
}
// Postfix
const Integer Integer::operator++(int)
{
Integer oldValue = *this;
++(*this);
return oldValue;
}
この例を見ると、前置演算子が後置演算子よりも常に効率的であることが簡単にわかります。postfixの使用には一時オブジェクトが必要なため。
これが、イテレータを使用する例を見るとき、常にプレフィックスバージョンを使用する理由です。
しかし、intについて指摘したように、コンパイラーの最適化が行われるため、実際には違いはありません。
マイクロ最適化が心配な場合の追加の観察結果を以下に示します。ループのデクリメントは、(おそらくARMのような命令セットアーキテクチャに応じて)インクリメントループよりも「効率的」です。
for (i = 0; i < 100; i++)
ループごとに、それぞれに対して1つの命令があります。
1
していi
ます。 i
がより小さいかどうかを比較し100
ます。i
がより小さい場合の条件付きブランチ100
。一方、デクリメントループ:
for (i = 100; i != 0; i--)
ループには次のそれぞれに対する命令があります。
i
、CPUレジスタステータスフラグを設定。Z==0
)。もちろん、これはゼロにデクリメントするときにのみ機能します!
ARMシステム開発者ガイドから覚えています。
どちらを使用するかは、「どちらが速いか」の問題にしないでください。そんなに気にすることは決してないだろうし、それに加えて、プログラマーの読み取り時間はマシン時間よりもはるかに高くつく。
コードを読む人間にとって最も意味のあるものを使用してください。
まず、C i++
との違い++i
はCではごくわずかです。
詳細へ。
++i
より速いC ++では、++i
より効率的なのi
は、オーバーロードされたインクリメント演算子を持つオブジェクトの一種です。
どうして?
では++i
、オブジェクトは最初にインクリメントされ、その後、他の関数へのconst参照として渡すことができます。これは、式が呼び出されるfoo(i++)
前にインクリメントを実行する必要があるために式がそうである場合は不可能ですfoo()
が、古い値をに渡す必要がありますfoo()
。その結果、コンパイラーは、i
、オリジナルでインクリメント演算子を実行する前にのます。追加のコンストラクタ/デストラクタ呼び出しは悪い部分です。
上記のように、これは基本的なタイプには適用されません。
i++
かもしれないコンストラクタ/デストラクタは、常にCの場合である、呼び出す必要がない場合、++i
およびi++
、右も同様に高速である必要がありますか?いいえ。それらは事実上同等に高速ですが、小さな違いがある可能性があり、他のほとんどの回答者が間違った方法で回避しました。
どうすれi++
ば速くなることができますか?
ポイントはデータの依存関係です。値をメモリからロードする必要がある場合、2つの後続の操作を使用して、値を増分し、使用する必要があります。では++i
、値を使用する前にインクリメントを行う必要があります。でi++
、使用が増加に依存せず、CPUは使用操作を実行することができる並列にインクリメント動作。違いはせいぜい1 CPUサイクルなので、それは実際には無視できますが、それはあります。そして、それは多くの人が期待するであろう逆の方法です。
++i
またはi++
が別の式内で使用されている場合、それらの間で変更すると式のセマンティクスが変更されるため、パフォーマンスの向上/損失の可能性は問題外です。それらがスタンドアロンである場合、つまり、操作の結果がすぐに使用されない場合、適切なコンパイラはそれを同じもの、たとえばINC
アセンブリ命令にコンパイルします。
i++
あり、++i
ループ定数を1つ調整することにより、ほぼすべての状況で互換的に使用できるため、プログラマーが行うこととほぼ同じです。2)どちらも同じ命令にコンパイルされますが、CPUの実行は異なります。の場合i++
、CPU は同じ値を使用する他のいくつかの命令と並行して増分を計算できます(CPUは実際にこれを実行します)。一方++i
、CPUでは、増分後に他の命令をスケジュールする必要があります。
if(++foo == 7) bar();
とif(foo++ == 6) bar();
機能的に同等です。ただし、比較と増分はCPUで並行して計算できるため、2番目の方が1サイクル速くなる場合があります。この単一のサイクルは重要ではありませんが、違いはあります。
<
vsなど<=
)に多く表示さ++
れるため、theth間の変換は、多くの場合簡単に可能です。
@Markコンパイラーは変数の(スタックベースの)一時的なコピーを最適化することを許可されており、gcc(最近のバージョンでは)は最適化していますが、すべてのコンパイラーが常に最適化するわけではありません。
現在のプロジェクトで使用しているコンパイラでテストしたところ、4つのうち3つは最適化していません。
コンパイラが正しく機能しているとは決して想定しないでください。特に、コードが読みやすくなる可能性はありますが、速度が遅くなる可能性がある場合は特にそうです。
コード内の演算子の1つの本当に愚かな実装がない場合:
Alwasはi ++よりも++ iを好みます。
Cでは、結果が未使用の場合、コンパイラーは通常、それらを同じになるように最適化できます。
ただし、C ++では、独自の++演算子を提供する他の型を使用している場合、接頭辞バージョンの方が後置バージョンよりも高速になる可能性があります。そのため、postfixセマンティクスが必要ない場合は、前置演算子を使用することをお勧めします。
接頭辞の増分よりも接尾辞が遅い状況を考えることができます。
レジスタ付きのプロセッサがA
アキュムレータとして使用され、多くの命令で使用される唯一のレジスタであると想像してください(いくつかの小さなマイクロコントローラは実際にはこのようなものです)。
次のプログラムと仮想アセンブリへの変換を想像してください。
プレフィックスの増分:
a = ++b + c;
; increment b
LD A, [&b]
INC A
ST A, [&b]
; add with c
ADD A, [&c]
; store in a
ST A, [&a]
Postfixの増分:
a = b++ + c;
; load b
LD A, [&b]
; add with c
ADD A, [&c]
; store in a
ST A, [&a]
; increment b
LD A, [&b]
INC A
ST A, [&b]
の値に注意してください b
に強制的にリロードされた。プレフィックスインクリメントを使用すると、コンパイラは値をインクリメントして使用を続行できます。インクリメント後に目的の値がすでにレジスタにあるため、リロードを回避できます。ただし、後置インクリメントでは、コンパイラーは2つの値を処理する必要があります。1つは古い値で、もう1つはインクリメントされた値で、上記で示したように、メモリアクセスが1つ増えます。
もちろん、単一のi++;
ステートメントのようにインクリメントの値が使用されない場合、コンパイラーはポストフィックスやプレフィックスの使用に関係なく、インクリメント命令を生成できます(実際に生成します)。
補足として、が存在する式は、追加の作業(たとえばを追加すること)なしでb++
は単純に式に変換できないことを述べておきます。したがって、これらが何らかの式の一部である場合に2つを比較しても、実際には有効ではありません。多くの場合、式の中で使用するとは使用できません。そのため、潜在的に効率がよくても、それは単に間違っているでしょう。もちろん、式が物乞いをしている場合は例外です(たとえば、これをに変更できます)。++b
- 1
b++
++b
++b
a = b++ + 1;
a = ++b;
私はここでほとんどの回答と多くのコメントを読んでいますが、どこがより効率的であると考えることができる1つのインスタンスへの参照は見られませんでした(そしておそらく驚くほどはより効率的でした)。これは、DEC PDP-11用のCコンパイラ用です。i++
++i
--i
i--
PDP-11には、レジスタの事前デクリメントとポストインクリメントのアセンブリ命令がありましたが、その逆はありませんでした。これらの命令により、「汎用」レジスタをスタックポインタとして使用できました。したがって、そのようなものを使用した場合*(i++)
、単一のアセンブリ命令にコンパイルできますが、*(++i)
できませんでした。
これは明らかに非常に難解な例ですが、ポストインクリメントがより効率的であるという例外を提供します(または、最近PDP-11 Cコードに対する需要があまりないため、私はと言った方がいいでしょう)。
--i
してi++
。
私は常にプリインクリメントを好みますが...
operator ++関数を呼び出す場合でも、関数がインライン化された場合、コンパイラーは一時変数を最適化することができることを指摘しておきたいと思います。operator ++は通常短く、ヘッダーに実装されることが多いため、インライン化される可能性があります。
したがって、実際には、2つのフォームのパフォーマンスに大きな違いはありません。ただし、オプティマイザに頼って判断するよりも、自分が言おうとしていることを直接表現する方が良いように思われるため、常に事前インクリメントを優先します。
また、optmizerの実行を少なくすると、コンパイラーの実行が速くなります。
私のCは少し錆びているので、お詫びします。Speedwise、私は結果を理解することができます。しかし、両方のファイルがどのように同じMD5ハッシュに出力されたかについて、私は混乱しています。多分forループは同じように実行されますが、次の2行のコードは異なるアセンブリを生成しませんか?
myArray[i++] = "hello";
対
myArray[++i] = "hello";
最初のものは配列に値を書き込み、次にiをインクリメントします。2番目のインクリメントは、配列に書き込みます。私はアセンブリの専門家ではありませんが、これらの2つの異なるコード行によって同じ実行可能ファイルがどのように生成されるかはわかりません。
ちょうど私の2セント。
foo[i++]
せfoo[++i]
ずにに変更すると、プログラムのセマンティクスが明らかに変更されますが、一部のプロセッサでは、ループホイスト最適化ロジックなしでコンパイラを使用する場合、インクリメントp
してq
1回実行すると、たとえば、を実行するループを*(p++)=*(q++);
使用するよりも高速になります*(++pp)=*(++q);
。一部のプロセッサでの非常にタイトなループの場合、速度の差は大きく(10%以上)なる可能性がありますが、Cでポストインクリメントがプリインクリメントよりも実質的に速い唯一のケースです。