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!


 

Comments (5)

  1. 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.

  2. RGabo says:

    use the Stopwatch class in 2.0 😉

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

  3. Chen Noam says:

    Great test, I’ve learn a lot.

    Thanks

  4. David Tolan says:

    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.