The code below is checking performance of three different ways to do same solution.
public static void Main(string[] args)
{
// for loop
{
Stopwatch sw = Stopwatch.StartNew();
int accumulator = 0;
for (int i = 1; i <= 100000000; ++i)
{
accumulator += i;
}
sw.Stop();
Console.WriteLine("time = {0}; result = {1}", sw.ElapsedMilliseconds, accumulator);
}
//Enumerable.Range
{
Stopwatch sw = Stopwatch.StartNew();
var ret = Enumerable.Range(1, 100000000).Aggregate(0, (accumulator, n) => accumulator + n);
sw.Stop();
Console.WriteLine("time = {0}; result = {1}", sw.ElapsedMilliseconds, ret);
}
//self-made IEnumerable<int>
{
Stopwatch sw = Stopwatch.StartNew();
var ret = GetIntRange(1, 100000000).Aggregate(0, (accumulator, n) => accumulator + n);
sw.Stop();
Console.WriteLine("time = {0}; result = {1}", sw.ElapsedMilliseconds, ret);
}
}
private static IEnumerable<int> GetIntRange(int start, int count)
{
int end = start + count;
for (int i = start; i < end; ++i)
{
yield return i;
}
}
}
The results are:
time = 306; result = 987459712
time = 1301; result = 987459712
time = 2860; result = 987459712
It is not surprising that the "for loop" is faster than the other two solutions, because Enumerable.Aggregate takes more method invocations. However, it really surprises me that "Enumerable.Range" is faster than the "self-made IEnumerable". I thought that Enumerable.Range would have more overhead than the simple GetIntRange method.
What are the possible reasons for this?
My guess is that you're running in a debugger. Here are my results, having built from the command line with "/o+ /debug-"
There's still a slight difference, but it's not as pronounced. Iterator block implementations aren't quite as efficient as a tailor-made solution, but they're pretty good.
Assuming this is a release build running, otherwise all comparisons are off as the JIT will not be working flat out.
You could look at the assembly with reflector and see what the 'yield' statement is being expanded too. The compiler will be creating a class to encapsulate the iterator. Maybe there is more housekeeping going on in the generated code than the implementation of Enumerable.Range which is likely hand-coded
A slight difference in the Reflector output (as well as the argument check and extra level of internalisation definitely not relevant here). The essential code is more like:
That is, instead of another local variable, they apply an extra addition for every yield.
I have tried to benchmark this, but I can't stop enough external processes to get understandable results. I also tried each test twice to ignore the effects of the JIT compiler, but even that has 'interesting' results.
Here's a sample of my results:
and the code
compiled with
Why should
Enumerable.Range
be any slower than your self-madeGetIntRange
? In fact, ifEnumerable.Range
were defined asthen it should be exactly as fast as your self-made
GetIntRange
. This is in fact the reference implementation forEnumerable.Range
, absent any tricks on the part of the compiler or programmer.You may want to compare your
GetIntRange
andSystem.Linq.Enumerable.Range
with the following implementation (of course, compile in release mode, as Rob points out). This implementation may be slightly optimized with respect to what a compiler would generate from an iterator block.