Popular Categories
« MSBUILD - item list flattening transform | Main | CodeMonkey song »
Thursday
Sep252008

Some performance issues and caveats of 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

There are no comments for this journal entry. To create a new comment, use the form below.

PostPost a New Comment

Enter your information below to add a new comment.

My response is on my own website »
Author Email (optional):
Author URL (optional):
Post:
 
Some HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>