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!