6

for ループが foreach ループよりも高速かどうかを調べようとしていて、System.Diagnostics クラスを使用してタスクの時間を計っていました。テストを実行しているときに、最初に配置したループは常に最後のループよりも遅く実行されることに気付きました。なぜこれが起こっているのか誰か教えてもらえますか?私のコードは以下の通りです:

using System;
using System.Diagnostics;

namespace cool {
    class Program {
        static void Main(string[] args) {
            int[] x = new int[] { 3, 6, 9, 12 };
            int[] y = new int[] { 3, 6, 9, 12 };

            DateTime startTime = DateTime.Now;
            for (int i = 0; i < 4; i++) {
                Console.WriteLine(x[i]);
            }
            TimeSpan elapsedTime = DateTime.Now - startTime;

            DateTime startTime2 = DateTime.Now;
            foreach (var item in y) {
                Console.WriteLine(item);
            }
            TimeSpan elapsedTime2 = DateTime.Now - startTime2;

            Console.WriteLine("\nSummary");
            Console.WriteLine("--------------------------\n");
            Console.WriteLine("for:\t{0}\nforeach:\t{1}", elapsedTime, elapsedTime2);

            Console.ReadKey();
      }
   }
}

出力は次のとおりです。

for:            00:00:00.0175781
foreach:        00:00:00.0009766
4

8 に答える 8

16

おそらく、最初からクラス (コンソールなど) を JIT コンパイルする必要があるためです。最初にすべてのメソッドを呼び出して (それらを JIT (ウォームしてからアップ) する)、次にテストを実行することで、最適なメトリックを取得できます。

他のユーザーが指摘したように、違いを示すには 4 回のパスでは不十分です。

ちなみに、 for と foreach のパフォーマンスの違いはごくわずかであり、 foreach を使用することによる読みやすさの利点は、ほとんどの場合、わずかなパフォーマンスの利点を上回ります。

于 2009-06-20T14:45:51.650 に答える
7
  1. パフォーマンスの測定に DateTime は使用しませんStopwatch。クラスを試してみてください。
  2. 4 パスだけで測定しても、良い結果が得られることはありません。> 100.000 パスを使用することをお勧めします (外側のループを使用できます)。Console.WriteLineあなたのループでやらないでください。
  3. さらに良い: プロファイラーを使用します (Redgate ANTS や NProf など)。
于 2009-06-20T14:42:57.117 に答える
3

私は C# にはあまり詳しくありませんが、私の記憶が正しければ、Microsoft は Java 用の「ジャスト イン タイム」コンパイラを作成していました。彼らが C# で同じまたは類似の手法を使用する場合、「2 番目に来るいくつかのコンストラクトがより高速に実行される」のはかなり自然なことです。

たとえば、JIT-System がループが実行されていることを確認し、アドホックにメソッド全体をコンパイルすることを決定する可能性があります。したがって、2 番目のループに到達すると、まだコンパイルされていないため、最初のループよりもはるかに高速に実行されます。しかし、これは私のかなり単純な推測です。もちろん、何が起こっているのかを理解するには、C# ランタイム システムではるかに優れた洞察が必要です。RAM ページが最初のループで最初にアクセスされ、2 番目のループではまだ CPU キャッシュにある可能性もあります。

アドオン: 作成されたもう 1 つのコメント: 出力モジュールを最初のループで初めて JIT できるということは、私の最初の推測よりも可能性が高いと思います。現代の言語は、内部で何が行われているかを調べるのが非常に複雑です。また、私のこの声明は、この推測に当てはまります。

しかし、ループには端末出力もあります。それらは物事をさらに困難にします。また、プログラムで最初に端末を開くのに時間がかかる場合もあります。

于 2009-06-20T14:47:28.183 に答える
2

foreach バージョンには、for ループには存在しないいくつかの形式のオーバーヘッドがある理由

  • IDisposable の使用。
  • すべての要素に対する追加のメソッド呼び出し。IEnumerator<T>.Current各要素は、メソッド呼び出しを使用して内部でアクセスする必要があります。インターフェイス上にあるため、インライン化できません。これは、N が列挙内の要素の数である N 個のメソッド呼び出しを意味します。for ループは、インデクサーを使用するだけです
  • foreach ループでは、すべての呼び出しがインターフェイスを通過します。一般に、これは具象型よりも少し遅くなります

上に挙げたものは、必ずしも莫大な費用ではないことに注意してください。通常、これらは非常に小さなコストであり、パフォーマンスのわずかな違いに寄与する可能性があります。

また、Mehrdad が指摘したように、コンパイラと JIT は、配列などの特定の既知のデータ構造に対して foreach ループを最適化することを選択する場合があることに注意してください。最終結果は単なる for ループになる可能性があります。

注: 一般に、パフォーマンス ベンチマークを正確にするには、もう少し作業が必要です。

  • DateTime の代わりに StopWatch を使用する必要があります。パフォーマンス ベンチマークでは、はるかに正確です。
  • 一度だけでなく、何度もテストを実行する必要があります
  • 初めてメソッドを JIT する際の問題を解消するために、ループごとにダミーの実行を行う必要があります。すべてのコードが同じメソッド内にある場合、これはおそらく問題にはなりませんが、害はありません。
  • リストで 4 つ以上の値を使用する必要があります。代わりに 40,000 を試してください。
于 2009-06-20T14:40:32.113 に答える
1

これについてはあまり詳しくは説明しません。次の理由から、これは適切なプロファイリング コードで
はありません。1. DateTime はプロファイリング用ではありません。CPU ハードウェア プロファイル カウンタを使用する QueryPerformanceCounter または StopWatch を使用する必要があります
2. Console.WriteLine はデバイス メソッドであるため、考慮すべきバッファリングなどの微妙な影響がある場合があります
3. 各コード ブロックを 1 回反復して実行すると、正確な結果が得られません。あなたのCPUはアウトオブオーダー実行や命令スケジューリングなどの多くのファンキーなオンザフライ最適化を行うため
です。ブロック

タイミングをよりよく理解するために、次のことを行いました

  1. Console.WriteLine を数式 ( e^num) に置き換えました
  2. P/Invoke を介して QueryPerformanceCounter/QueryPerformanceTimer を使用しました
  3. 各コード ブロックを 100 万回実行し、結果を平均しました

私がそれをしたとき、私は次の結果を得ました:

for ループにかかった時間は 0.000676 ミリ秒
foreach ループにかかった時間は 0.000653 ミリ秒

したがって、 foreach は非常にわずかに高速でしたが、それほどではありませんでした

次に、さらにいくつかの実験を行い、最初に foreach ブロックを実行し、次に for ブロックを実行すると
、次の結果が得られました。

foreach ループにかかった時間は 0.000702 ミリ秒
for ループにかかった時間は 0.000691 ミリ秒

最後に、両方のループを一緒に 2 回実行しまし
た。

foreach ループにかかった時間は 0.00140 ミリ秒
for ループにかかった時間は 0.001385 ミリ秒

したがって、基本的には、2 番目に実行するコードはどれもわずかに速く実行されるように見えますが、意味をなすには十分ではありません。
--編集--
ここに役立つリンクがいくつかあります
QueryPerformanceCounter を使用してマネージド コード
の時間を測定する方法 命令キャッシュ
アウト オブ オーダー実行

于 2009-06-20T15:20:13.407 に答える
1

ストップウォッチを使用して動作の時間を計る必要があります。

技術的にはforループの方が高速です。Foreachは、変数をインクリメントするだけでよい場合、IEnumerable の反復子で MoveNext() メソッドを呼び出します (呼び出しからメソッド スタックとその他のオーバーヘッドを作成します) 。

于 2009-06-20T14:46:43.630 に答える