のようなものを使用する間にパフォーマンスの違いはありますか
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コンパイラーが特定のコンテキストでいくつかの最適化を実行して、あるバージョンを他のバージョンよりも優先する可能性があります(ほとんどありませんが)。ただし、そのような最適化がある場合は、ループの最後(またはおそらく最初)の反復にのみ影響を与える可能性があります。
要するに、あなたが説明したループ構造の制御変数の単純なプリインクリメントまたはポストインクリメントの実行時間に違いはありません。
ああ...また開いて。 OK。これが契約です。
ILDASMは始まりですが、終わりではありません。重要な点は、JITがアセンブリコードに対して何を生成するかです。
これがあなたがしたいことです。
あなたが見ようとしているもののいくつかのサンプルを取ります。もちろん、必要に応じて壁時計時刻を設定することもできますが、それ以上のことを知りたいと思います。
明らかではないことは次のとおりです。 C#コンパイラは、多くの状況で最適ではないいくつかのMSILシーケンスを生成します。これらと他の言語からの癖に対処するために調整したJIT。問題:誰かが気づいた「癖」だけが調整されました。
試してみる実装があり、メイン(またはどこでも)、Sleep()、またはデバッガーを接続できる場所に戻ってから、ルーチンを再度実行するサンプルを作成する必要があります。
デバッガーでコードを開始したくない場合、JITは最適化されていないコードを生成します。実際の環境でどのように動作するかを知りたいようです。 JITはこれを行って、デバッグ情報を最大化し、「ジャンプ」による現在のソースの場所を最小化します。デバッガーでパフォーマンス評価を開始しないでください。
OK。したがって、コードが1回実行されたら(つまり、JITがそのコードを生成した場合)、スリープ中にデバッガーを接続します(またはその他)。次に、2つのルーチン用に生成されたx86/x64を確認します。
私の直感によると、説明したように++ i/i ++を使用している場合、つまり、右辺値の結果が再利用されないスタンドアロン式では、違いはありません。しかし、すべての素敵なものを見つけて見に行くのは楽しいことではありません! :)
この質問をしている場合は、間違った問題を解決しようとしています。
最初に尋ねる質問は、「ソフトウェアをより高速に実行することで、ソフトウェアに対する顧客満足度を向上させる方法」です。答えはほとんど「i ++の代わりに++ iを使用する」またはその逆です。
CodingHorrorの投稿から " ハードウェアは安価で、プログラマーは高価です ":
最適化のルール:
ルール1:しないでください。
ルール2(専門家のみ):まだ実行しないでください。
- M.A。ジャクソン
私はルール2を読んで、「最初に顧客のニーズを満たすクリーンで明確なコードを記述し、次に遅すぎる場合は高速化する」ことを意味します。 ++i
対i++
が解決策になります。
Jim Mischel 表示済み のように、コンパイラーはforループを記述する2つの方法に対して同一のMSILを生成します。
しかし、それだけです。JITについて推測したり、速度測定を実行したりする理由はありません。 2行のコードが同一のMSILを生成する場合、それらは同一に実行されるだけでなく、事実上同一です。
可能なJITはループを区別できないため、生成されるマシンコードも必ず同一である必要があります。
みんな、みんな、「答え」はCとC++のためのものです。
C#は別の動物です。
ILDASMを使用してコンパイル済み出力を調べ、MSILに違いがあるかどうかを確認します。
具体的なコードとCLRリリースを念頭に置いていますか?もしそうなら、それをベンチマークします。そうでない場合は、それを忘れてください。マイクロ最適化、およびそのすべて...さらに、異なるCLRリリースが同じ結果を生成することを確信することさえできません。
他の回答に加えて、違いがある可能性がありますあなたのi
がint
でない場合。 C++の場合、演算子++()
および++(int)
がオーバーロードされているクラスのオブジェクトである場合、違いが生じ、場合によっては副作用が発生する可能性があります。 。のパフォーマンス ++i
shouldこの場合はより良いです(実装によって異なります)。
この回答 によると、i ++は++ iよりも1つのCPU命令を使用します。しかし、これがパフォーマンスの違いをもたらすかどうかはわかりません。
どちらのループもポストインクリメントまたはプリインクリメントのいずれかを使用するように簡単に書き直すことができるので、コンパイラーは常により効率的なバージョンを使用すると思います。
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回の実行の平均:
for i ++:1307 for with ++ i:1314
while i ++の場合:1261 ++ iの場合:1276
それは2,53 GhzのCeleron Dです。各反復には約1.6CPUサイクルかかりました。これは、CPUが各サイクルで複数の命令を実行していたか、JITコンパイラがループを展開したことを意味します。 i ++と++ iの違いは、おそらくバックグラウンドのOSサービスが原因で、反復あたりわずか0.01CPUサイクルでした。