Cでi ++と++ iのパフォーマンスに違いはありますか?
-
09-06-2019 - |
質問
結果の値を使用しない場合、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);
}
これらのファイルは、<=>と<=>を除いて同じです:
$ 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
他のヒント
Andrew Koenigによる効率対意図より:
まず、少なくとも整数変数が関係する場合、
++i
がi++
よりも効率的であることは明らかではありません。
そして:
だから、質問するべきは、これらの2つの操作のどちらが高速であるかではなく、これら2つの操作のどちらがあなたが達成しようとしていることをより正確に表現するかです。式の値を使用していない場合は、変数の値をコピーして変数をインクリメントし、その後コピーを捨てます。
したがって、結果の値が使用されない場合、<=>を使用します。しかし、それがより効率的だからではなく、それは私の意図を正しく述べているからです。
より良い答えは、++i
は時々速くなりますが遅くはならないことです。
i
はint
などの通常の組み込み型であるとみなしているようです。この場合、測定可能な違いはありません。
ただし、i++
が複合型の場合、測定可能な違いが見つかる可能性があります。 ++it
については、増分する前にクラスのコピーを作成する必要があります。 <=>を使用すると最終値を返すことができるため、コピーに含まれる内容によっては実際に遅くなる場合があります。
Foo Foo::operator++()
{
Foo oldFoo = *this; // copy existing value - could be slow
// yadda yadda, do increment
return oldFoo;
}
別の違いは、<=>を使用すると、値の代わりに参照を返すオプションがあることです。繰り返しますが、オブジェクトのコピーの作成に関係するものによっては、これは遅くなる可能性があります。
これが発生する可能性のある実際の例は、反復子の使用です。イテレータのコピーがアプリケーションのボトルネックになることはほとんどありませんが、結果に影響しない<=>の代わりに<=>を使用する習慣を身に付けることをお勧めします。
Scott Meyersから葉を取り、より効果的なc ++ アイテム6:インクリメント操作とデクリメント操作のプレフィックス形式とポストフィックス形式を区別します。
オブジェクト、特にイテレータに関しては、接尾辞よりも接頭辞バージョンが常に優先されます。
演算子の呼び出しパターンを見た場合のこの理由。
// Prefix
Integer& Integer::operator++()
{
*this += 1;
return *this;
}
// Postfix
const Integer Integer::operator++(int)
{
Integer oldValue = *this;
++(*this);
return oldValue;
}
この例を見ると、接頭辞演算子が接尾辞よりも常に効率的であることが簡単にわかります。接尾辞の使用時に一時オブジェクトが必要なため。
これが、反復子を使用する例を見るとき、常に接頭辞バージョンを使用する理由です。
しかし、intについて指摘するように、コンパイラの最適化が行われるため、実質的に違いはありません。
マイクロ最適化について心配している場合の追加の観察結果を次に示します。ループのデクリメントは、ループのインクリメントよりも「おそらく」効率的です(ARMなどの命令セットアーキテクチャによって異なります)。
for (i = 0; i < 100; i++)
各ループで、次のそれぞれに対して1つの命令があります。
-
1
をi
に追加しています。 -
100
がZ==0
より小さいかどうかを比較します。 - <=>が<=>より小さい場合の条件分岐。
デクリメントループ:
for (i = 100; i != 0; i--)
ループには、それぞれの命令があります:
- デクリメント<=>、CPUレジスタステータスフラグの設定。
- CPUレジスタステータスに応じた条件分岐(<=>)。
もちろん、これはゼロまでデクリメントする場合にのみ機能します!
『ARMシステム開発者ガイド』から覚えています。
簡単な答え:
速度の点でi++
と++i
の間に違いはありません。優れたコンパイラーは、2つのケースで異なるコードを生成するべきではありません。
長答:
他のすべての答えに言及していないのは、for(i=0; i<n; i++)
とi
の違いは、見つかった式の中でのみ意味があるということです。
1
の場合、++
は独自の式で単独です。array[i++] = x;
の前にシーケンスポイントがあり、その後に1つあります。したがって、生成される唯一のマシンコードは<!> quot; increase array[++i] = x;
by <=> <!> quot;そして、これがプログラムの残りの部分に関連してどのようにシーケンスされるかは明確に定義されています。したがって、接頭辞<=>に変更する場合でも、それはわずかな問題ではなく、マシンコード<!> quot; increase <=> by <=> <!> quot;を取得するだけです。
<=>と<=>の違いは、<=>対<=>などの式でのみ重要です。一部の人は、<=>が存在するレジスタを後でリロードする必要があるため、このような操作では接尾辞が遅くなると主張して言うかもしれません。ただし、コンパイラは、<!> quot;抽象的なマシンの動作を破壊しない限り、命令を自由に命令することができます。 C標準ではそれを呼び出しています。
したがって、<=>は次のようにマシンコードに変換されると仮定できます:
- レジスタAに<=>の値を格納します。
- レジスタBに配列のアドレスを格納します。
- AとBを追加し、結果をAに保存します。
- Aで表されるこの新しいアドレスに、xの値を格納します。
- <=>の値をレジスタAに保存します。
- レジスタAをインクリメントします。
- レジスタAを<=>に保存します。
コンパイラは、次のようなコードをより効率的に生成する可能性があります。
- レジスタAに<=>の値を格納します。
- レジスタBに配列のアドレスを格納します。
- AとBを追加し、結果をBに保存します。
- レジスタAをインクリメントします。
- レジスタAを<=>に保存します。
- ... //コードの残り。
Cプログラマとして、接尾辞<=>は最後に起こると考えるように訓練されているので、マシンコードはそのように注文する必要はありません。
したがって、Cのプレフィックスとポストフィックス<=>の間に違いはありません。Cプログラマーとしてのあなたの違いは、理由を付けずに、場合によってはプレフィックスを使用し、他のケースではポストフィックスを使用することです。これは、Cがどのように機能するかについて不確かであること、または言語に関する誤った知識があることを示唆しています。これは常に悪い兆候であり、迷信や<!> quot; religious dogmas <!> quot;に基づいて、プログラムで他の疑わしい決定を下していることを示唆しています。
<!> quot;プレフィックス<=>は常に高速です<!> quot;実際、Cプログラマーによく見られるこのような誤った教義の1つです。
<!> quot;どちらが速い<!> quot;の質問をさせないでください。使用する決定要因になります。そんなに気にすることはないでしょうし、プログラマの読書時間はマシン時間よりもはるかに高価です。
コードを読む人間にとって最も意味のあるものを使用してください。
まず:Cではi++
と++i
の違いは無視できます。
詳細へ。
1。よく知られているC ++の問題:i
は高速です
C ++では、foo(i++)
は、オーバーロードされたインクリメント演算子を備えた何らかのオブジェクトである場合に、より効率的です。
なぜ?
foo()
では、オブジェクトは最初にインクリメントされ、その後、他の関数へのconst参照として渡すことができます。式が<=>の場合、これは不可能です。これは、<=>が呼び出される前にインクリメントを実行する必要がありますが、古い値を<=>に渡す必要があるためです。したがって、コンパイラは、元のインクリメント演算子を実行する前に<=>のコピーを作成することを強制されます。追加のコンストラクタ/デストラクタ呼び出しは悪い部分です。
上記のように、これは基本型には適用されません。
2。あまり知られていない事実:<=> 速くなる可能性があります
コンストラクター/デストラクターを呼び出す必要がない場合(Cの場合は常にそうです)、<=>と<=>は同等に高速である必要があります。いいえ。それらは実質的に同等に高速ですが、小さな違いがあるかもしれません。他のほとんどの回答者は間違った方法で回避しました。
どのようにして<=>を高速化できますか?
ポイントはデータの依存関係です。値をメモリからロードする必要がある場合、2つの後続の操作を実行して、値を増やし、使用する必要があります。 <=>を使用すると、値を使用する前に 増分を行う必要があります。 <=>を使用すると、使用は増分に依存せず、CPUは増分操作と並行して使用操作 を実行できます。違いはせいぜい1 CPUサイクルであるため、実際には無視できますが、実際にはあります。そして、それは多くの人が期待する逆の方法です。
@Mark コンパイラーは変数の(スタックベースの)一時コピーを最適化することを許可されていますが、gcc(最近のバージョンでは)はそうしています。 すべてのコンパイラが常にそうすることを意味しません。
現在のプロジェクトで使用しているコンパイラでテストしたところ、4つのうち3つが最適化されていません。
特に高速であるが低速ではないコードの方が読みやすい場合は特に、コンパイラが正しいと仮定しないでください。
コード内の演算子のいずれかの本当に愚かな実装がない場合:
常にi ++よりも++ iが優先されます。
Cでは、コンパイラは一般に、結果が使用されていない場合に同じになるように最適化できます。
ただし、C ++では、独自の++演算子を提供する他の型を使用する場合、プレフィックスバージョンはポストフィックスバージョンよりも高速になる可能性があります。そのため、後置セマンティクスが不要な場合は、プレフィックス演算子を使用することをお勧めします。
後置が接頭辞の増分より遅い状況を考えることができます:
レジスタ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]
後置インクリメント:
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++
がある式は、追加の努力なしで(たとえば++b
を追加することによって)- 1
を含む式に簡単に変換できないことに言及したいと思います。したがって、ある式の一部である場合に2つを比較することは実際には有効ではありません。多くの場合、式内でa = b++ + 1;
を使用する場合、a = ++b;
は使用できないため、<=>の方が潜在的に効率的であっても、単に間違っているだけです。もちろん、式がそれを求めている場合は例外です(たとえば、<=>は<=>に変更できます)。
私は常にプリインクリメントを好むが、...
operator ++関数を呼び出した場合でも、関数がインライン化された場合、コンパイラーは一時を最適化することができることを指摘したかったのです。 operator ++は通常短く、ヘッダーに実装されることが多いため、インライン化される可能性があります。
したがって、実際的な目的のために、2つのフォームのパフォーマンスにはほとんど違いはありません。ただし、オプティマイザに頼って判断するよりも、私が言いたいことを直接表現する方が良いと思われるため、常に事前インクリメントを好みます。
また、オプティマイザーの実行回数を減らすと、コンパイラーの実行速度が速くなります。
私のCは少しさびているので、事前に謝罪します。迅速に、結果を理解できます。しかし、私は両方のファイルが同じMD5ハッシュにどのように出てきたかについて混乱しています。たぶんforループは同じように動作しますが、次の2行のコードは異なるアセンブリを生成しませんか?
myArray[i++] = "hello";
vs
myArray[++i] = "hello";
最初のものは値を配列に書き込み、次にiをインクリメントします。 2番目のインクリメントiは、配列に書き込みます。私はアセンブリの専門家ではありませんが、これらの2つの異なるコード行によって同じ実行可能ファイルがどのように生成されるかわかりません。
ちょうど2セントです。