Some performance issues and caveats of LINQ
Thursday, September 25, 2008 at 2:18 in
Linq 20% of the code executes 80% of the time
As you may already know, Linq to collections generally performs worse, than the hard-coded approach. That happens because we deal with the iterator classes and lazy evaluation.
Most of the time this is not a problem, since the impact on the overall performance is negligible. And the code itself gets much cleaner and easier to read.
However, in the high-stress scenarios (i.e.: in heavy math calculations) improper Linq usage could become a problem. Let's write a couple of micro-benchmarks and compare the performance of hand-written loops with the Linq.
In simple selection and sum Linq works just x1.5 times slower:
// Loop - 01.73 s
for (int j = 0; j < array.Length; j++)
{
var v = array[j].Value;
if (v > Math.PI)
{
sum1 += v;
}
}
// LINQ - 02.54 s
sum2 += array.Where(o => o.Value > Math.PI).Sum(o => o.Value);
But the difference could get bigger (x2.6), if we forget, that Linq does the lazy evaluation:
// Loop - 01.77 s
for (int j = 0; j < array.Length; j++)
{
var v = array[j].Value;
if (v > Math.PI)
{
sum1 += v;
total1 += 1;
}
}
// Linq - 04.60 s
var doubles = array.Where(o => o.Value > Math.PI);
total2 += doubles.Count();
sum2 += doubles.Sum(o => o.Value);
One could be tempted to use .ToArray() to force the evaluation once, but this has its own cost and will degrade the performance even more (x3.1):
// Linq - 05.43 s
for (int i = 0; i < 100000; i++)
{
var doubles = array
.Select(o => o.Value)
.Where(d => d > Math.PI)
.ToArray();
total2 += doubles.Count();
sum2 += doubles.Sum();
}
Resume: Linq is sharp. There is a lot of stuff going inside these simple looking extension methods for collections. Just be careful with them, while writing performance-critical code, and you should be fine.
Appendix: these micro-snippets have been executed against array generated like this:
var r = new Random(1);
var array = Enumerable.Range(1, 1000)
.Select(i => new
{
ID = i,
Name = "Item_" + i,
Category = "Category_" + (i%10),
Value = r.NextDouble()*10
})
.ToArray();
Note: we use randSeed parameter to create repeatable experiment results.
Every statement was compiled in "Release" mode and executed 100000 times. Total execution time was measured with the Stopwatch class.
Reader Comments