Friday, June 27, 2008

Loop optimization trickery

A while ago I was playing around with C# 3.5 lambda expressions trying to work out how it behaves with variable scope.

What do you think is written to the console?
class Program
{
static void Main(string[] args)
{
// Write number to console in worker thread
for (int i = 1; i < 9; i++)
DoLater(() => Console.Write(i));
}

// Call action delegate on separate thread
static void DoLater(Action action)
{
// I'm avoiding using another lambda here to simplify test
new Thread(new ParameterizedThreadStart(DoLater)).Start(action);
}

// Calls the action after waiting for a second
static void DoLater(object action)
{
Thread.Sleep(1000);
((Action)action)();
}
}

If you initially guessed (like me) that it displays the numbers 1-9 in some random order (eg. '14385726') - you'd be wrong...   it actually produces '999999999' every time.

So I cranked up .net reflector (I love it) and had a look at my code (optimization set to .NET 1.0 - then translated by me):
private static void Main(string[] args)
{
AutoDelegateClass tmpVar = new AutoDelegateClass();
tmpVar.i = 1;
while (tmpVar.i < 9)
{
DoLater(new Action(tmpVar.Method));
tmpVar.i++;
}
}

This is much more obvious...  .net optimizes the lambda expression out of the loop!

Well that was unexpected, but guess what happens when I change main to this:
static void Main(string[] args)
{
for (int i = 1; i < 9; i++)
{
int j = i;
DoLater(() => Console.Write(j));
}
}

This works! ie. it does not optimize the lambda expression out of the loop.


Although confusing at first, it actually makes a lot of sense from the compiler point of view - the initial expression doesn't actually change, since we are referencing a mutable variable... so why not optimize it?

It might be convenient to turn off this optimization programmatically - kind of like a volatile keyword or something, but it's no big deal.

I guess it goes to show that functional code works best with immutable data... and that reflector is way cool.


BTW: The exact same thing occurs in foreach over a collection; and also using classes rather than structs.

No comments: