2

I have following two loops in C#, and I am running these loops for a collection with 10,000 records being downloaded with paging using "yield return"

First

foreach(var k in collection) {
  repo.Save(k);
}

Second

 var collectionEnum = collection.GetEnumerator();
 while (collectionEnum.MoveNext()) {
   var k = collectionEnum.Current;
   repo.Save(k);
   k = null;
 }

Seems like that the second loop consumes less memory and it faster than the first loop. Memory I understand may be because of k being set to null(Even though I am not sure). But how come it is faster than for each.

Following is the actual code

  [Test]
    public void BechmarkForEach_Test() {
        bool isFirstTimeSync = true;
        Func<Contact, bool> afterProcessing = contactItem => {
            return true;
        };

        var contactService = CreateSerivce("/administrator/components/com_civicrm");
        var contactRepo = new ContactRepository(new Mock<ILogger>().Object);
        contactRepo.Drop();
        contactRepo = new ContactRepository(new Mock<ILogger>().Object);

        Profile("For Each Profiling",1,()=>{
            var localenumertaor=contactService.Download();
            foreach (var item in localenumertaor) {

            if (isFirstTimeSync)
                item.StateFlag = 1;

            item.ClientTimeStamp = DateTime.UtcNow;

            if (item.StateFlag == 1)
                contactRepo.Insert(item);
            else
                contactRepo.Update(item);

            afterProcessing(item);


        }
        contactRepo.DeleteAll();
        });

    }


    [Test]
    public void BechmarkWhile_Test() {
        bool isFirstTimeSync = true;
        Func<Contact, bool> afterProcessing = contactItem => {
                                                                 return true;
        };

        var contactService = CreateSerivce("/administrator/components/com_civicrm");
        var contactRepo = new ContactRepository(new Mock<ILogger>().Object);
        contactRepo.Drop();
        contactRepo = new ContactRepository(new Mock<ILogger>().Object);

        var itemsCollection = contactService.Download().GetEnumerator();

        Profile("While Profiling", 1, () =>
            {
                while (itemsCollection.MoveNext()) {

                    var item = itemsCollection.Current;
                    //if First time sync then ignore and overwrite the stateflag
                    if (isFirstTimeSync)
                        item.StateFlag = 1;

                    item.ClientTimeStamp = DateTime.UtcNow;

                    if (item.StateFlag == 1)
                        contactRepo.Insert(item);
                    else
                        contactRepo.Update(item);

                    afterProcessing(item);

                    item = null;
                }
                contactRepo.DeleteAll();

            });
    }

    static void Profile(string description, int iterations, Action func) {

        // clean up
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();

        // warm up 
        func();

        var watch = Stopwatch.StartNew();
        for (int i = 0; i < iterations; i++) {
            func();
        }
        watch.Stop();
        Console.Write(description);
        Console.WriteLine(" Time Elapsed {0} ms", watch.ElapsedMilliseconds);
    }

I m using the micro bench marking, from a stackoverflow question itself benchmarking-small-code

The time taken is

  • For Each Profiling Time Elapsed 5249 ms
  • While Profiling Time Elapsed 116 ms
Community
  • 1
  • 1
Mohit
  • 914
  • 4
  • 14
  • 28
  • 2
    Seems unlikely, can you disassemble both loops with [ILSpy](http://wiki.sharpdevelop.net/ILSpy.ashx) and compare the result? – Albin Sunnanbo Jul 01 '12 at 05:26
  • You say download and save, those operations are typically heavy and can vary between each execution, have you done multiple measurements and analyzed average and variance? – Albin Sunnanbo Jul 01 '12 at 05:28
  • 1
    Is collection strongly typed? e.g. A generic collection. I'm wondering if 'repo.Save' method has overloads and different methods are called. – Rob Smyth Jul 01 '12 at 05:35
  • 4
    Setting `k` to `null` has no effect at all on memory usage (unless you run it in debug mode...). The garbage collector already knows that the reference is not used any more even before you assign null to the variable. – Guffa Jul 01 '12 at 05:48
  • There are two differences that I can point out for certain here. First, in "second" (unlike "first") there is no exception handling around the call to `MoveNext` that will be found in code generated for a `foreach`. Second, `k` is declared inside the loop in "second" where in a `foreach` it will be declared outside (hence the warnings for "access to modified closure" when capturing that variable in an expression). Can't speak to why second would be necessarily faster though - wouldn't happen to be a 32-bit app would it? – mlorbetske Jul 01 '12 at 07:30
  • 3
    How does it "seem"? How are you measuring? Actually, the second is *worse*, as you aren't disposing the enumerator. – Marc Gravell Jul 01 '12 at 07:50
  • 1
    @mlorbetske I'd have to check, but I don't recall any specific exception handling around MoveNext() in the expansion. Just a try/finally (essentially a "using") to dispose the enumerator. Edit: I checked, and indeed, tht isn't there: http://www.jaggersoft.com/csharp_standard/15.8.4.htm – Marc Gravell Jul 01 '12 at 07:51
  • @marc that's what I was referring to, you're right, I should have been more clear – mlorbetske Jul 01 '12 at 07:53
  • I have updated the question. Please have a look at the actual code – Mohit Jul 01 '12 at 13:54

1 Answers1

3

Your foreach version calls var localenumertaor = contactService.Download(); inside the profile action, while the enumerator version calls it outside of the Profile call.

On top of that, the first execution of the iterator version will exhaust the items in the enumerator, and on subsequent iterations itemsCollection.MoveNext() will return false and skip the inner loop entirely.

Lee
  • 142,018
  • 20
  • 234
  • 287
  • Thanks a ton Lee for this catch this was a typo let me fix and then compare.. thanks .. – Mohit Jul 01 '12 at 15:28