14

私は同僚と少し論争をしました(これは聖戦に非常に近いものでした:))。いくつかの事実を操作するために、私は次のテストを作成しました。

   static void Main(string[] args)
    {
        const int count = 10000000;

        var stopwatch = new Stopwatch();

        var list = new List<int>(count);

        var rnd = new Random();

        for (int i = 0; i < count; i++)
        {
            list.Add( rnd.Next());
        }

        const int repeat = 20;

        double indeces = 0;
        double forEach = 0;

        for (int iteration = 0; iteration < repeat; iteration++)
        {
            stopwatch.Restart();
            long tmp = 0;
            for (int i = 0; i < count; i++)
            {                    
                tmp += list[i];
            }

            indeces += stopwatch.Elapsed.TotalSeconds;
            stopwatch.Restart();
            foreach (var integer in list)
            {            
                tmp += integer;
            }

            forEach += stopwatch.Elapsed.TotalSeconds;
        }

        Console.WriteLine(indeces /repeat);
        Console.WriteLine(forEach /repeat);

    }

実際には、要素にアクセスするだけです。

予想通り、インデックスアクセスは高速でした。これは私のマシンでのリリースビルドの結果です:

    0.0347//index access
    0.0737//enumerating

ただし、テストを少し変更することにしました。

        //the same as before
        ...
        IEnumerable<int> listAsEnumerable = list;
        //the same as before
        ...
        foreach (var integer in listAsEnumerable)
        {                
            tmp += integer;
        }
        ...

そして今、出力は次のとおりでした:

    0.0321//index access
    0.1246//enumerating (2x slower!)

インターフェイスを介して同じリストを列挙している場合、パフォーマンスは 2倍遅くなります。

なぜこれ*が起こっているのですか?

これは、「実際のリストを列挙するよりも2倍遅いインターフェースを介して列挙する」ことを意味します。

私の推測では、ランタイムは異なるを使用していますEnumerator。最初のテストではリストが使用され、2番目のテストでは一般的なリストが使用されています。

4

4 に答える 4

16

を使用する場合List<T>、は実際にはインターフェイスforeachを使用しません。IEnumerable<T>むしろ、を使用します。List<T>.Enumeratorこれはstructです。些細なレベルでは、これは間接参照がわずかに少なくなり、参照を解除する必要がなく、仮想呼び出しではなく静的呼び出しを使用することを意味し、より直接的な実装になります。

これらの違いは非常に小さく、実際の実用的な例では、違いはノイズです。ただし、パフォーマンスだけをテストする場合は、わずかに目立つ場合がありforeachます。

これを拡張するには:実際foreachには必要ありませんIEnumerable[<T>]-純粋GetEnumerator()///パターンで機能し.MoveNext()ます。これは、2.0のジェネリックス以前は特に重要でした。.Current.Dispose()

ただし、これは、変数がList<T>(カスタムGetEnumerator()メソッドを持つ)として入力された場合にのみ可能です。あなたが持っているIEnumerable<T>と、それは使用する必要がありますIEnumerator<T>

于 2012-05-17T11:21:10.387 に答える
4

あなたはここでコードを見ることができます:

static void Main()
{

    List<int> list = new List<int>(Enumerable.Range(1,10000));

    int total = 0;
    foreach (var i in list)
    {
        total += i;
    }
    IEnumerable<int> enumerable = list;
    foreach (var i in enumerable)
    {
        total += i;
    }
    Console.ReadLine();
}

これはこのILを生成します。の違いに注意してください

System.Collections.Generic.List`1/Enumerator<int32>

System.Collections.Generic.IEnumerable`1<int32> 

ValueTypeそしてそれが(構造体)であることに注意してください:

.method private hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       146 (0x92)
  .maxstack  2
  .locals init ([0] class [mscorlib]System.Collections.Generic.List`1<int32> list,
           [1] int32 total,
           [2] int32 i,
           [3] class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> enumerable,
           [4] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32> CS$5$0000,
           [5] bool CS$4$0001,
           [6] class [mscorlib]System.Collections.Generic.IEnumerator`1<int32> CS$5$0002)
  IL_0000:  nop
  IL_0001:  ldc.i4.1
  IL_0002:  ldc.i4     0x2710
  IL_0007:  call       class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> [System.Core]System.Linq.Enumerable::Range(int32,
                                                                                                                                  int32)
  IL_000c:  newobj     instance void class [mscorlib]System.Collections.Generic.List`1<int32>::.ctor(class [mscorlib]System.Collections.Generic.IEnumerable`1<!0>)
  IL_0011:  stloc.0
  IL_0012:  ldc.i4.0
  IL_0013:  stloc.1
  IL_0014:  nop
  IL_0015:  ldloc.0
  IL_0016:  callvirt   instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> class [mscorlib]System.Collections.Generic.List`1<int32>::GetEnumerator()
  IL_001b:  stloc.s    CS$5$0000
  .try
  {
    IL_001d:  br.s       IL_002d
    IL_001f:  ldloca.s   CS$5$0000
    IL_0021:  call       instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::get_Current()
    IL_0026:  stloc.2
    IL_0027:  nop
    IL_0028:  ldloc.1
    IL_0029:  ldloc.2
    IL_002a:  add
    IL_002b:  stloc.1
    IL_002c:  nop
    IL_002d:  ldloca.s   CS$5$0000
    IL_002f:  call       instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::MoveNext()
    IL_0034:  stloc.s    CS$4$0001
    IL_0036:  ldloc.s    CS$4$0001
    IL_0038:  brtrue.s   IL_001f
    IL_003a:  leave.s    IL_004b
  }  // end .try
  finally
  {
    IL_003c:  ldloca.s   CS$5$0000
    IL_003e:  constrained. valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>
    IL_0044:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
    IL_0049:  nop
    IL_004a:  endfinally
  }  // end handler
  IL_004b:  nop
  IL_004c:  ldloc.0
  IL_004d:  stloc.3
  IL_004e:  nop
  IL_004f:  ldloc.3
  IL_0050:  callvirt   instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> class [mscorlib]System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator()
  IL_0055:  stloc.s    CS$5$0002
  .try
  {
    IL_0057:  br.s       IL_0067
    IL_0059:  ldloc.s    CS$5$0002
    IL_005b:  callvirt   instance !0 class [mscorlib]System.Collections.Generic.IEnumerator`1<int32>::get_Current()
    IL_0060:  stloc.2
    IL_0061:  nop
    IL_0062:  ldloc.1
    IL_0063:  ldloc.2
    IL_0064:  add
    IL_0065:  stloc.1
    IL_0066:  nop
    IL_0067:  ldloc.s    CS$5$0002
    IL_0069:  callvirt   instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
    IL_006e:  stloc.s    CS$4$0001
    IL_0070:  ldloc.s    CS$4$0001
    IL_0072:  brtrue.s   IL_0059
    IL_0074:  leave.s    IL_008a
  }  // end .try
  finally
  {
    IL_0076:  ldloc.s    CS$5$0002
    IL_0078:  ldnull
    IL_0079:  ceq
    IL_007b:  stloc.s    CS$4$0001
    IL_007d:  ldloc.s    CS$4$0001
    IL_007f:  brtrue.s   IL_0089
    IL_0081:  ldloc.s    CS$5$0002
    IL_0083:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
    IL_0088:  nop
    IL_0089:  endfinally
  }  // end handler
  IL_008a:  nop
  IL_008b:  call       string [mscorlib]System.Console::ReadLine()
  IL_0090:  pop
  IL_0091:  ret
} // end of method Program2::Main
于 2012-05-17T11:27:11.770 に答える
2

両方のバージョンのILを見ると、最初のバージョンがタイプのイテレーター(System.Collections.Generic.List<System.Int32>+Enumeratorネストされた)を使用していることがわかりますstruct。これは、リストを反復処理するために最適化されています。

2番目のバージョンは、の汎用実装を使用しますSystem.Collections.Generic.IEnumerator<System.Int32>。これは、リスト内の現在のアイテムへのプライベートインデックスを保持することによって「チート」しないため、効率が低下します。

于 2012-05-17T11:22:40.293 に答える
1

foreachの代わりにforを使用するとパフォーマンスが向上すると思います(少なくともプリミティブ型の場合)。私の知る限り、同じ配列に対してforとforeachを実行する場合、それらはほぼ同等です(リストのような他の構造ではなく、これはそれ自体でいくつかのオーバーヘッドを作成します)。

foreachとforのパフォーマンスは、実行している構造のタイプとforeachによって異なります。

チェックしてください; ForとForeachの比較

于 2012-05-17T11:22:39.043 に答える