次のようなものを使用することの間にパフォーマンスの違いはありますか
for(int i = 0; i < 10; i++) { ... }
と
for(int i = 0; i < 10; ++i) { ... }
または、コンパイラは、機能的に同等の場合に同等に高速になるように最適化できますか?
編集:これは、同僚と話し合ったために尋ねられたものであり、実用的な意味での最適化に役立つと思うからではありません。それは主に学術的なものです。
次のようなものを使用することの間にパフォーマンスの違いはありますか
for(int i = 0; i < 10; i++) { ... }
と
for(int i = 0; i < 10; ++i) { ... }
または、コンパイラは、機能的に同等の場合に同等に高速になるように最適化できますか?
編集:これは、同僚と話し合ったために尋ねられたものであり、実用的な意味での最適化に役立つと思うからではありません。それは主に学術的なものです。
この場合、生成される中間コードは ++i と i++ で違いはありません。このプログラムを考えると:
class Program
{
const int counter = 1024 * 1024;
static void Main(string[] args)
{
for (int i = 0; i < counter; ++i)
{
Console.WriteLine(i);
}
for (int i = 0; i < counter; i++)
{
Console.WriteLine(i);
}
}
}
生成された IL コードは、両方のループで同じです。
IL_0000: ldc.i4.0
IL_0001: stloc.0
// Start of first loop
IL_0002: ldc.i4.0
IL_0003: stloc.0
IL_0004: br.s IL_0010
IL_0006: ldloc.0
IL_0007: call void [mscorlib]System.Console::WriteLine(int32)
IL_000c: ldloc.0
IL_000d: ldc.i4.1
IL_000e: add
IL_000f: stloc.0
IL_0010: ldloc.0
IL_0011: ldc.i4 0x100000
IL_0016: blt.s IL_0006
// Start of second loop
IL_0018: ldc.i4.0
IL_0019: stloc.0
IL_001a: br.s IL_0026
IL_001c: ldloc.0
IL_001d: call void [mscorlib]System.Console::WriteLine(int32)
IL_0022: ldloc.0
IL_0023: ldc.i4.1
IL_0024: add
IL_0025: stloc.0
IL_0026: ldloc.0
IL_0027: ldc.i4 0x100000
IL_002c: blt.s IL_001c
IL_002e: ret
とはいえ、JIT コンパイラーが特定のコンテキストで、あるバージョンを他のバージョンよりも優先するいくつかの最適化を実行できる可能性は (ほとんどありませんが) あります。ただし、そのような最適化がある場合、ループの最後の (またはおそらく最初の) 反復にのみ影響を与える可能性があります。
要するに、説明したループ構造の制御変数の単純なプリインクリメントまたはポストインクリメントの実行時間に違いはありません。
この質問をしている場合は、間違った問題を解決しようとしています。
最初に尋ねる質問は、「ソフトウェアをより高速に実行することで、ソフトウェアに対する顧客満足度を向上させるにはどうすればよいか?」ということです。答えは、「i++ の代わりに ++i を使用する」またはその逆ではありません。
Coding Horror の投稿 " Hardware is Cheap, Programmers are Expensive " から:
最適化のルール:
ルール 1: やらない。
ルール 2 (専門家のみ): まだ実行しないでください。
-- MA ジャクソン
ルール 2 は、「最初に顧客のニーズを満たすクリーンで明確なコードを記述し、次に遅すぎるところを高速化する」という意味であると読みました。++i
vs.が解決策になる可能性はほとんどありませんi++
。
Jim Mischelが示したように、コンパイラはforループを記述する2つの方法に対して同一のMSILを生成します。
しかし、それだけです。JITについて推測したり、速度測定を実行したりする理由はありません。2行のコードが同一のMSILを生成する場合、それらは同一のパフォーマンスを発揮するだけでなく、事実上同一です。
可能なJITはループを区別できないため、生成されるマシンコードも必ず同一である必要があります。
ああ…もう一度開いてください。わかった。これが取引です。
ILDASM は始まりですが、終わりではありません。重要なのは、JIT はアセンブリ コードに対して何を生成するかということです。
これがあなたがしたいことです。
あなたが見ようとしているもののいくつかのサンプルを取ります。明らかに、必要に応じてそれらの時間を計ることができますが、それ以上のことを知りたいと思います。
明らかでないことは次のとおりです。C# コンパイラは、多くの状況で最適でない MSIL シーケンスを生成します。これらと他の言語の癖に対処するために調整された JIT。問題: 誰かが気づいた「癖」だけが調整されています。
実装を試して、メイン (またはどこでも)、Sleep()、またはデバッガーをアタッチできる何かに戻り、ルーチンを再度実行するサンプルを本当に作成したいと考えています。
デバッガーの下でコードを開始したくない場合は、JIT が最適化されていないコードを生成します。実際の環境でどのように動作するかを知りたいようです。JIT はこれを実行して、デバッグ情報を最大化し、現在のソースの場所が「飛び回る」のを最小限に抑えます。デバッガーでパフォーマンス評価を開始しないでください。
わかった。したがって、コードが 1 回実行されると (つまり、JIT がコードを生成した後)、スリープ中 (またはその他) にデバッガーをアタッチします。次に、2 つのルーチン用に生成された x86/x64 を見てください。
あなたが説明したように ++i/i++ を使用している場合、つまり、右辺値の結果が再利用されないスタンドアロン式で使用している場合、違いはありません。でも、素敵なものを見つけて見に行くのは楽しいですよね!:)
具体的なコードと CLR リリースを念頭に置いていますか? もしそうなら、それをベンチマークしてください。そうでない場合は、忘れてください。マイクロ最適化、およびそのすべて... さらに、異なる CLR リリースが同じ結果を生成するかどうかさえ確認できません。
他の回答に加えて、あなたi
がint
. C++ では++()
、それが演算子を持ちオーバーロードされたクラスのオブジェクトである++(int)
場合、違いが生じる可能性があり、場合によっては副作用が生じる可能性があります。この場合、のパフォーマンスは向上する++i
はずです (実装によって異なります)。
この回答によると、i++ は ++i よりも多くの CPU 命令を 1 つ使用します。しかし、これがパフォーマンスの違いにつながるかどうかはわかりません。
どちらのループもポストインクリメントまたはプリインクリメントを使用するように簡単に書き直すことができるため、コンパイラは常により効率的なバージョンを使用すると思います。
static void Main(string[] args) {
var sw = new Stopwatch(); sw.Start();
for (int i = 0; i < 2000000000; ++i) { }
//int i = 0;
//while (i < 2000000000){++i;}
Console.WriteLine(sw.ElapsedMilliseconds);
3 回の実行の平均:
i++ の場合: 1307 ++i の場合: 1314
while with i++ : 1261 while with ++i : 1276
それは 2,53 Ghz の Celeron D です。各反復には、約 1.6 CPU サイクルがかかりました。これは、CPU が各サイクルで複数の命令を実行していたか、JIT コンパイラーがループを展開したことを意味します。i++ と ++i の違いは、反復あたりわずか 0.01 CPU サイクルであり、おそらくバックグラウンドの OS サービスが原因です。
両方に違いはありません。理由は、ループ比較では、インクリメント/デクリメント ステートメントの後に別のステートメントがあるためです。例 ;
for(int i=0; i<3; i++){
System.out.println(i)
}
これは以下のように動作します。
ステップの初期化:i = 0
Step Compare : i < 3
true の場合、loop
ブロックを実行
ステップ増分 :i = i++;
またはi = ++i;
Step Copmare Again : Java が次のステップで比較を行っているため。i++ と ++i の両方で同じ結果が得られます。たとえば、i = 0 の場合、インクリメント後、i は 1 になります。
そのため、プリインクリメントまたはポストインクリメントがループ内で同じように動作します。