Freigeben über


SYSK 132: Loop Performance Comparison -- foreach vs. for

Do you think there is a performance difference between the following two loops?

foreach (int x in _data)

{

            . . .

}

 

vs.

for (int i = 0; i < _data.Count; i++)

{

            . . .

}

 

My tests show that foreach is slower than the for loop.  The larger the collection you’re looping through, the greater is the performance difference…

 

With 1 mln records, it’s about 2 times slower; at 10 mln records it’s about 3.5-4 times slower.  Having said that, we’re still talking about milliseconds; so in comparison with the other work your code is executing in the loop, the difference in the actual loop performance is negligible.

Comments

  • Anonymous
    May 23, 2006
    Below is a silly mock of of possible IL code. It looks to me that you would probably want to compare a collection of Objects rather than boxed ints.


    private static void Loop1()
    {
         foreach (int num1 in Program._data)
         {
         }
    }

    .method private hidebysig static void Loop1() cil managed
    {
         .maxstack 2
         .locals init (
               [0] int32 num1,
               [1] [mscorlib]System.Collections.IEnumerator enumerator1,
               [2] bool flag1,
               [3] [mscorlib]System.IDisposable disposable1)
         L_0000: nop
         L_0001: nop
         L_0002: ldsfld [mscorlib]System.Collections.IList TestLoops.Program::_data
         L_0007: callvirt instance [mscorlib]System.Collections.IEnumerator [mscorlib]System.Collections.IEnumerable::GetEnumerator()
         L_000c: stloc.1
         L_000d: br.s L_001d
         L_000f: ldloc.1
         L_0010: callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current()
         L_0015: unbox.any int32
         L_001a: stloc.0
         L_001b: nop
         L_001c: nop
         L_001d: ldloc.1
         L_001e: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
         L_0023: stloc.2
         L_0024: ldloc.2
         L_0025: brtrue.s L_000f
         L_0027: leave.s L_0040
         L_0029: ldloc.1
         L_002a: isinst [mscorlib]System.IDisposable
         L_002f: stloc.3
         L_0030: ldloc.3
         L_0031: ldnull
         L_0032: ceq
         L_0034: stloc.2
         L_0035: ldloc.2
         L_0036: brtrue.s L_003f
         L_0038: ldloc.3
         L_0039: callvirt instance void [mscorlib]System.IDisposable::Dispose()
         L_003e: nop
         L_003f: endfinally
         L_0040: nop
         L_0041: ret
         .try L_000d to L_0029 finally handler L_0029 to L_0040
    }



    private static void Loop2()
    {
         for (int num1 = 0; num1 < Program._data.Count; num1++)
         {
         }
    }

    .method private hidebysig static void Loop2() cil managed
    {
         .maxstack 2
         .locals init (
               [0] int32 num1,
               [1] bool flag1)
         L_0000: nop
         L_0001: ldc.i4.0
         L_0002: stloc.0
         L_0003: br.s L_000b
         L_0005: nop
         L_0006: nop
         L_0007: ldloc.0
         L_0008: ldc.i4.1
         L_0009: add
         L_000a: stloc.0
         L_000b: ldloc.0
         L_000c: ldsfld [mscorlib]System.Collections.IList TestLoops.Program::_data
         L_0011: callvirt instance int32 [mscorlib]System.Collections.ICollection::get_Count()
         L_0016: clt
         L_0018: stloc.1
         L_0019: ldloc.1
         L_001a: brtrue.s L_0005
         L_001c: ret
    }
  • Anonymous
    May 23, 2006
    What if you manually code the foreach loop as:

    for (x = _data.GetEnumerator(); x.MoveNext();)
    {
       ...
    }


    Also, what type of collection were you iterating over? An array? The for() loop presumably calls Item() to get each member of the collection, which should be O(1) per iteration on arrays, but O(n) per iteration on lists. However, foreach() should be O(1) per iteration (albeit a slower O(1) than for for()) on both types of collection.

    For that reason, foreach() should be faster than for() for large lists. Possibly a lot faster.
  • Anonymous
    May 23, 2006
    Coach:

    You should probably change the for loop to:

    for (int i = 0; i < Program._data.Count; ++i)
    {
       int num1 = Program._data.Item(i);
    }

    so that you access each item in the collection in both loops. That will probably make a fairer comparison.
  • Anonymous
    May 24, 2006
    I would have assumed the foreach would have been slower do to overheard.. boxing of int32, plus the another class that implemenets IEnumerator is being used to perform the numeration. I wasn't expecting it to 2x and 4x slower though :/
  • Anonymous
    May 24, 2006
    Adam is obviously correct. Also to compare objects to objects rather than boxed ints in one case.

    Here is a silly little program with IL in comments. Would you really use statics? No.

    Probably the way to do this would to run it in Performce testing tools (I did in VS2005), but the exporting is more than I can do right now.

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Collections;

    namespace TestLoops
    {
       class Program
       {
           static object obj;
           static IList _dataObjs = new System.Collections.ArrayList();
           static int ListSize;
           static string loopType = "ForEach";

           static void Main(string[] args)
           {
               ListSize = Convert.ToInt32(args[0]);
               LoadList();

               loopType = args[1];
               if (loopType == "ForEach")
               {
                   ForEachLoop(_dataObjs);
               }
               else
               {
                   ForLoop(_dataObjs);
               }
           }

           // For Each Loop
           static void ForEachLoop(IList data)
           {
               foreach (object x in data)
               {
                   obj = x;
               }
           }
    //.method private hidebysig static void ForEachLoop([mscorlib]System.Collections.IList data) cil managed
    //{
    //      .maxstack 1
    //      .locals init (
    //            [0] object obj1,
    //            [1] [mscorlib]System.Collections.IEnumerator enumerator1,
    //            [2] [mscorlib]System.IDisposable disposable1)
    //      L_0000: ldarg.0
    //      L_0001: callvirt instance [mscorlib]System.Collections.IEnumerator [mscorlib]System.Collections.IEnumerable::GetEnumerator()
    //      L_0006: stloc.1
    //      L_0007: br.s L_0016
    //      L_0009: ldloc.1
    //      L_000a: callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current()
    //      L_000f: stloc.0
    //      L_0010: ldloc.0
    //      L_0011: stsfld object TestLoops.Program::obj
    //      L_0016: ldloc.1
    //      L_0017: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
    //      L_001c: brtrue.s L_0009
    //      L_001e: leave.s L_0031
    //      L_0020: ldloc.1
    //      L_0021: isinst [mscorlib]System.IDisposable
    //      L_0026: stloc.2
    //      L_0027: ldloc.2
    //      L_0028: brfalse.s L_0030
    //      L_002a: ldloc.2
    //      L_002b: callvirt instance void [mscorlib]System.IDisposable::Dispose()
    //      L_0030: endfinally
    //      L_0031: ret
    //      .try L_0007 to L_0020 finally handler L_0020 to L_0031
    //}

           // For Loop
           static void ForLoop(IList data)
           {
               for (int i = 0; i < data.Count; i++)
               {
                   obj = data[i];
               }
           }
    //.method private hidebysig static void ForLoop([mscorlib]System.Collections.IList data) cil managed
    //{
    //      .maxstack 2
    //      .locals init (
    //            [0] int32 num1)
    //      L_0000: ldc.i4.0
    //      L_0001: stloc.0
    //      L_0002: br.s L_0014
    //      L_0004: ldarg.0
    //      L_0005: ldloc.0
    //      L_0006: callvirt instance object [mscorlib]System.Collections.IList::get_Item(int32)
    //      L_000b: stsfld object TestLoops.Program::obj
    //      L_0010: ldloc.0
    //      L_0011: ldc.i4.1
    //      L_0012: add
    //      L_0013: stloc.0
    //      L_0014: ldloc.0
    //      L_0015: ldarg.0
    //      L_0016: callvirt instance int32 [mscorlib]System.Collections.ICollection::get_Count()
    //      L_001b: blt.s L_0004
    //      L_001d: ret
    //}

           static void LoadList()
           {
               
               for (int i = 0; i < ListSize; i++)
               {
                   _dataObjs.Add(new Object());
               }
           }

       }
    }

    For what it is worth, here is some performce data that I got - Showing Elapsed Inclusive Numbers

    Loop 1000        10000       100000
    Foreach 0.710204    5.010083    45.182694
    For     0.396586    6.634075    50.831526

    Very Quickly Done. I suggest running it yourself if interested - and making modifications to the use non-static scenarios.

  • Anonymous
    May 24, 2006
    My tests show that a foreach loop of a value collection (int array in my test) is virually the same speed, an average difference of less than a second (foreach being slower) for 1 billion iterations.

    Object arrays, on the other hand are quicker with a foreach loop rather than a for loop by a difference of over 5 seconds for 1 billion iterations, foreach being ~45% faster.
  • Anonymous
    May 25, 2006
    Below is the actual test case I was using:

    public partial class Form1 : Form
    {
       long[] _data = new long[10000000];

       public Form1()
       {
           InitializeComponent();

           for (long i = 0; i < _data.Length; i++)
           {
               _data[i] = i;
           }
       }

       private void button1_Click(object sender, EventArgs e)
       {
           long t1, t2;
           long value;
           
           t1 = DateTime.Now.Ticks;            
           foreach (long item in _data)
           {
               value = item;
           }
           t2 = DateTime.Now.Ticks;
           System.Diagnostics.Debug.WriteLine(string.Format("foreach took {0} ticks", (t2-t1)));

           t1 = DateTime.Now.Ticks;            
           for (long i = 0; i < _data.Length; i++)
           {
               value = _data[i];
           }
           t2 = DateTime.Now.Ticks;
           System.Diagnostics.Debug.WriteLine(string.Format("for took {0} ticks", (t2-t1)));

       }
    }



    The result:
       foreach took 2804032 ticks
       for took 1101584 ticks

    As you can see, in both cases I get to the actual object in the array, so, I do believe, that the comparison is "fair".
  • Anonymous
    May 25, 2006
    I went so far as to yank the call to _data.Length (or my equivalent) out of the loop into a local variable, and the for loop is still about the same or slower over 1 billion iterations on my computer
  • Anonymous
    May 25, 2006
    Tone is hard to guage in posts. So I just want to be clear that none of my posts were meant as criticism of the original post. My posts are here because I found the topic interesting, something rather fundamental, and something I did not know about.

    I just want to add that I recently had to rebuild my Feed Subscriptions. In the past I had always liked the posts titled "SYSK:" that I got as part of blogs.msdn feed. So, when rebuilding, I now have this feed separated out, and have subscribed to the comment feed for this post - great feature! and great posts. Thanks for the great posts!
  • Anonymous
    May 25, 2006
    Clearly boxing of value types is affecting the results

    Using Object instead of long (Int64), I get the following results with your test case, where foreach is the same speed as for (5 runs):
           mean    median  mode
    foreach 468,756 468,756 468,756
    for     406,255 468,756 468,756

    I modified your test case as follows (changing TEST_TYPE where appropriate):
    using TEST_TYPE = System.Object;
    //...
    TEST_TYPE[] arr = new TEST_TYPE[10000000];

    for (int i = 0; i < arr.Length; i++)
    {
    arr[i] = new TEST_TYPE();
    }

    long t1, t2;
    TEST_TYPE value;
    int c = 0;
    t1 = DateTime.Now.Ticks;
    foreach (TEST_TYPE item in arr)
    {
    value = item;
    }
    t2 = DateTime.Now.Ticks;
    System.Diagnostics.Debug.WriteLine(string.Format("foreach took {0} ticks", (t2 - t1)));
    c = 0;

    t1 = DateTime.Now.Ticks;
    for (int i = 0; i < arr.Length; i++)
    {
    value = arr[i];
    }
    t2 = DateTime.Now.Ticks;
    System.Diagnostics.Debug.WriteLine(string.Format("for took {0} ticks", (t2 - t1)));
  • Anonymous
    May 26, 2006
    No worries.  I value all feedback -- positive and negative!
  • Anonymous
    May 26, 2006
    Makes sense.  Thanks for the post.
  • Anonymous
    May 26, 2006
    No Problem.  I just noticed the formatting of the code got lost, sorry about that.
  • Anonymous
    July 13, 2006
    C# vs VB.NET -> interesting

    VB code:
    Imports OBJ_T = System.Object

    Module Module1

    Sub Main()
    Dim arr(10000000) As OBJ_T

    For i As Integer = 0 To UBound(arr)
    arr(i) = New OBJ_T
    Next


    Dim t1, t2 As Long
    Dim value As OBJ_T
    t1 = DateTime.Now.Ticks
    For Each item As OBJ_T In arr
    value = item
    Next
    t2 = DateTime.Now.Ticks
    System.Diagnostics.Debug.WriteLine(String.Format("foreach: {0}", t2 - t1))

    t1 = DateTime.Now.Ticks
    For i As Integer = 0 To UBound(arr)
    value = arr(i)
    Next
    t2 = DateTime.Now.Ticks
    System.Diagnostics.Debug.WriteLine(String.Format("for: {0}", t2 - t1))

    Dim ii As Integer = 0
    t1 = DateTime.Now.Ticks
    For ii = 0 To UBound(arr)
    value = arr(ii)
    Next
    t2 = DateTime.Now.Ticks
    System.Diagnostics.Debug.WriteLine(String.Format("for same mem: {0}", t2 - t1))
    End Sub

    End Module


    C# code:

    using System;
    using TEST_TYPE = System.Object;

    namespace ConsoleApplication1
    {
       class Program
       {
           static void Main(string[] args)
           {
               //...
               TEST_TYPE[] arr = new TEST_TYPE[10000000];

               for (int i = 0; i < arr.Length; i++)
                   arr[i] = new TEST_TYPE();

               long t1, t2;
               TEST_TYPE value;
               t1 = DateTime.Now.Ticks;
               foreach (TEST_TYPE item in arr)
                   value = item;

               t2 = DateTime.Now.Ticks;
               System.Diagnostics.Debug.WriteLine(string.Format("foreach: {0}", t2-t1));

               t1 = DateTime.Now.Ticks;
               for (int i = 0; i < arr.Length; i++)
                   value = arr[i];

               t2 = DateTime.Now.Ticks;
               System.Diagnostics.Debug.WriteLine(string.Format("for: {0}", t2-t1));

               int ii = 0;
               t1 = DateTime.Now.Ticks;
               for (ii = 0; ii < arr.Length; ii++)
                   value = arr[ii];
               t2 = DateTime.Now.Ticks;
               System.Diagnostics.Debug.WriteLine(string.Format("for same mem: {0}", t2 - t1));


           }
       }
    }


    Results:
    VB:
    foreach: 7812500
    for: 3750000
    for same mem: 3906250

    C#:
    foreach: 781250
    for: 468750
    for same mem: 468750
  • Anonymous
    January 18, 2009
    PingBack from http://www.hilpers.it/2627042-for-o-for-each