Welcome to MSDN Blogs Sign in | Join | Help

SYSK 149: Performance Analysis of the ‘yield return’ Statement

In the “old days”, if you wanted to expose a way for users of a custom collection class to enumerate through the elements of the collection, you had to implement a custom enumerator (e.g. foreach (Car car in cars) { … } )

 

In .NET 2.0 (C#), there is a new yield keyword that does that for you…  If you’re not familiar with it, check out http://msdn.microsoft.com/msdnmag/issues/04/05/C20/ or just do a search for ‘yield and C#’ using your favorite search engine…

 

While there are many articles talking about what it is, I have not come across any that talk about any performance numbers.  My hunch was that the performance should be about the same when compared to a well written custom enumerator implementation. So, I created a simple test to prove that:

 

public partial class Form1 : Form

{

    private ListWithYield _data1 = new ListWithYield();

    private ListWithoutYield _data2 = new ListWithoutYield();

 

    public Form1()

    {

        InitializeComponent();           

    }

 

    private void Form1_Load(object sender, EventArgs e)

    {

        for (int i = 0; i < 1000000; i++)

        {

            _data1[i] = i.ToString();

            _data2[i] = i.ToString();

        }

    }

 

    private void button1_Click(object sender, EventArgs e)

    {

        string test;

 

        long t1, t2;

        t1 = DateTime.Now.Ticks;

 

        foreach (string item in _data1)

        {

            test = item;

        }

 

        t2 = DateTime.Now.Ticks;

        System.Diagnostics.Debug.WriteLine(string.Format("Custom with Yield  {0}", (t2 - t1)));

    }

 

    private void button2_Click(object sender, EventArgs e)

    {

        string test;

 

        long t1, t2;

        t1 = DateTime.Now.Ticks;

 

        foreach (string item in _data2)

        {

            test = item;

        }

 

        t2 = DateTime.Now.Ticks;

        System.Diagnostics.Debug.WriteLine(string.Format("Custom without Yield  {0}", (t2 - t1)));

    }       

 

   

}

 

// Note: can't use inheritance since GetEnumerator is not a virtual method

[Serializable()]

public class ListWithYield : System.Collections.Generic.IEnumerable<string>

{

    private string[] _data = new string[1000000];

 

    public string this[int index]

    {

        get { return _data[index]; }

        set { _data[index] = value; }

    }

 

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()

    {

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

        {

            yield return _data[i];

        }

    }

 

    public System.Collections.Generic.IEnumerator<string> GetEnumerator()

    {

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

        {

            yield return _data[i];

        }

    }

}

 

[Serializable()]

public class ListWithoutYield : System.Collections.Generic.IEnumerable<string>

{

    private string[] _data = new string[1000000];

 

    public string this[int index]

    {

        get { return _data[index]; }

        set { _data[index] = value; }

    }

 

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()

    {

        return new StringIter(this);

    }

 

    public System.Collections.Generic.IEnumerator<string> GetEnumerator()

    {

        return new StringIter(this);

    }

 

    [Serializable()]

    public struct StringIter : System.Collections.Generic.IEnumerator<string>, System.Collections.IEnumerator

    {

        private int _index;

        private int _size;

        private ListWithoutYield _list;

 

        public StringIter(ListWithoutYield list)

        {

            _list = list;

            _size = list._data.Length;

            _index = 0;

        }

 

        public bool MoveNext()

        {

            _index += 1;

 

            return _index < _size;               

        }

 

        void System.Collections.IEnumerator.Reset()

        {

            _index = 0;

        }

 

        public object Current

        {

            get { return _list._data[_index]; }

        }

 

        string System.Collections.Generic.IEnumerator<string>.Current

        {

            get { return _list._data[_index]; }

        }

 

        public void Dispose()

        {

           

        }

    }

}

 

The result confirm, you can write less code with yield return and expect same performance as with custom enumerator. 

Custom with Yield  1001440

Custom without Yield  1001440

Custom with Yield  1001440

Custom without Yield  901296

Custom with Yield  1001440

Custom without Yield  1001440

Custom with Yield  1001440

Custom without Yield  901296

Custom with Yield  901296

Custom without Yield  1001440

 

The compiler developers have done a great job!

 

Published Wednesday, July 05, 2006 7:01 AM by irenak

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# SYSK 149: Performance Analysis of the ‘yield return’ Statement

Wednesday, July 05, 2006 10:25 AM by This is not a life saving device

# re: SYSK 149: Performance Analysis of the ‘yield return’ Statement

Wednesday, July 05, 2006 1:52 PM by Gabriel Lozano-Morán
Hello Irena

I took the liberty to use a high performance counter to perform the same test and here are the results.

Custom without Yield  93118470
Custom with Yield  129995897
Custom without Yield  93057677
Custom with Yield  128973697
Custom without Yield  99645200
Custom with Yield  133408250
Custom without Yield  93149970
Custom with Yield  128061030
Custom without Yield  92982930
Custom with Yield  128352140
Custom without Yield  97940690
Custom with Yield  138568885
Custom without Yield  93221120
Custom with Yield  132528437

As you can see the difference between for example the last two are 39,307,317 ticks which converted to secs are 0,019555879104477611940298507462687 (on my hardware) which is considering the number of performed loops very neglictable.

# re: SYSK 149: Performance Analysis of the ‘yield return’ Statement

Monday, July 17, 2006 11:56 AM by RGabo
use the Stopwatch class in 2.0 ;)

Cool series of tips on your blog, reading them all!

# re: SYSK 149: Performance Analysis of the ‘yield return’ Statement

Saturday, June 14, 2008 6:35 PM by Chen Noam

Great test, I've learn a lot.

Thanks

# re: SYSK 149: Performance Analysis of the ‘yield return’ Statement

Tuesday, March 10, 2009 1:06 PM by David Tolan

BEWARE.

Yield does not play well with recursive algorithms.

Yield makes the implementation of a binary tree walker very simple.You get hit with the set-up costs of Yield at every iteration.

I rewrote a tree iterator to keep a stack in the iterator object, and push and pop the nodes as appropriate, takes 8% of the time to traverse a tree that the yield version took.

Leave a Comment

(required) 
required 
(required) 

  
Enter Code Here: Required
 
Page view tracker