Performance Tip-O-The-Day #1 : for[each]
This starts a new series (I hope) of performance tips that you may/may not know about.
This post is brought to you by Microsoft Mobile Development Handbook, in which I found this gem. It's on page 217 at the bottom if you have the book.
The basic idea is that foreach is slower than a for loop. I've pretty much always known this, but never really known how much slower it is. There's some overhead, sure, but I've always assumed it was negligible.
For this demonstration, I'm going to be targeting the full .NET framework as opposed to the compact framework just so I can take advantage of a couple classes that aren't available in the compact framework, but the idea applies to both.
So here's the basic test code.
void Test(List<int> data)
{
TimeSpan start, end;
int total, max, milliseconds;
Process p = Process.GetCurrentProcess();
total = 0;
start = p.TotalProcessorTime;
// start test
foreach (int i in data)
{
total += i;
}
// end test
end = p.TotalProcessorTime;
milliseconds = end.Subtract(start).TotalMilliseconds;
Console.WriteLine("foreach : " + milliseconds + " ms");
total = 0;
max = data.Count;
start = p.TotalProcessorTime;
// start test
for (int ctr = 0; ctr < max; ctr++)
{
total += data[ctr];
}
// end test
end = p.TotalProcessorTime;
milliseconds = end.Subtract(start).TotalMilliseconds;
Console.WriteLine("foreach : " + milliseconds + " ms");
}
This code just takes a list of integers and then does a foreach loop and a for loop, calculating (roughly) the time for each.
Next, on to the driver code.
// 100,000,000
int DataSetSize = 100000000;
int Runs = 10; try
{
List<int> dataset = new List<int>(DataSetSize);
for (int i = 0; i < DataSetSize; i++)
{
dataset.Add(i);
}
for (int i = 0; i < Runs; i++)
{
Test(dataset);
Console.WriteLine();
}
}
catch (OutOfMemoryException)
{
Console.WriteLine("Please shrink DataSetSize.");
}
I arbitrarily decided to run this 5 times. The first time is always slower, and that's expected. The OutOfMemoryException is in there because I made the data set a little bit too big sometimes and kept on crashing the process harshly.
Anyways, the results (in Release mode) are :
| foreach loop | for loop |
| Test Run #1 | 1250 | 187.5 |
| Test Run #2 | 984.375 | 187.5 |
| Test Run #3 | 765.625 | 218.75 |
| Test Run #4 | 828.125 | 203.125 |
| Test Run #5 | 765.625 | 218.75 |
All times are in milliseconds and are on my arbitrary workstation (nothing special on it : Vista, Visual Studio 2005). Note that the for loop can be 3 times faster than the foreach. Obviously, it depends how many items you're iterating over, but unless you really have to (i.e. the class only provides an iterator), I'd suggest not using the foreach.
As always, thoughts, comments, and questions are always welcome.
Edit (2007-07-06 11:18am PST) : Minor code fix, it's always a good idea to pass an initial size to Lists, among others.