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!
http://blogs.msdn.com/irenak/archive/2006/07/05/656898.aspx
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.
use the Stopwatch class in 2.0 😉
Cool series of tips on your blog, reading them all!
Great test, I’ve learn a lot.
Thanks
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.