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
Anonymous
July 05, 2006
http://blogs.msdn.com/irenak/archive/2006/07/05/656898.aspxAnonymous
July 05, 2006
The comment has been removedAnonymous
July 17, 2006
use the Stopwatch class in 2.0 ;)
Cool series of tips on your blog, reading them all!Anonymous
June 14, 2008
Great test, I've learn a lot. ThanksAnonymous
March 10, 2009
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.