Candy Chiu
Candy Chiu

Reputation: 6679

Linq Performance: (ElementAt,Count) vs (foreach)

I iterate through an IEnumerable as the result of a Linq query using (ElementAt,Count) and (foreach). To my surprise, the performance difference is 25-30 fold! Why is that?

IEnumerable<double> result =
     ... simple Linq query that joins two tables
     ... returns about 600 items

double total = 0;

// Method 1: iterate with Count and ElementAt
for( int i = 0; i < result.Count(); i++ )
{
    total += result.ElementAt(i);
}

// Method 2: iterate with foreach
foreach( double value in result )
{
    total += value;
}

Upvotes: 9

Views: 12022

Answers (7)

allthingscs
allthingscs

Reputation: 297

This is all about understanding deferred execution! When a query is executed several times, then the time to run increases dramatically. LINQ can be quicker, but you really need to make a choice based on how you're going to use your query results.

Take a look at this article http://allthingscs.blogspot.com/2011/03/linq-performance.html. It analyzes that very issue .

Upvotes: 1

Jon Hanna
Jon Hanna

Reputation: 113282

The first may be equivalent to:

double total = 0;

int i = 0;
while(true)
{
  int max = /*Database call to obtain COUNT(*) of join*/
  if(max > i)
    break;
  int j = 0;
  foreach(double value in result)
  {
    if(j++ == i)
    {
      total += value;
      break;
    }
  }
  ++i
}

Or it could even be equivalent to:

double total = 0;

int i = 0;
while(true)
{
  int max = 0;
  foreach(double value in result)
    ++max;
  if(max > i)
    break;
  int j = 0;
  foreach(double value in result)
  {
    if(j++ == i)
    {
      total += value;
      break;
    }
  }
  ++i
}

Or it could even requery to get result each time it appers in the code above.

On the other hand, Count() could be obtained by one property access, and ElementAt() could be O(1), if these are backed on structures that allow for such an optimisation, and such an optimisation was indeed available (e.g. it is for List<T>).

Upvotes: 0

Jodrell
Jodrell

Reputation: 35716

Because ElementAt is iterating through the IEnumerable every time you call it. IEnumerables are not indexed so ElementAt must be implemented using GetEnumerator().

Why not do

total = result.Sum();

Upvotes: 6

Gary.S
Gary.S

Reputation: 7121

If i were to hazard a guess, the result.Count() call is non-deferred and actually hits the database whereas the foreach does not. If you flip the order you may get the opposite result. Also you could just do total = result.Sum();

Upvotes: 0

rossipedia
rossipedia

Reputation: 59387

The ElementAt() method is O(n), unless the actual concrete class that the IEnumerable represents optimizes it. That means that every time you call it, it has to loop through the entire Enumerable to find the element at n. Not to mention that since you have i < result.Count() in the condition part of your for loop, it's gotta loop through the entire enumerable every single time to get that count.

The second way, you loop through result exactly once.

Upvotes: 13

rsbarro
rsbarro

Reputation: 27339

The performance difference is due to the fact the IEnumerable doesn't allow you to get by index, so every time you call ElementAt in your first loop it has to iterate through every item until it gets to the element you requested.

Upvotes: 1

Ritch Melton
Ritch Melton

Reputation: 11598

Because each call iterates over the list. The simple solution is to call .ToList() before iterating, a better solution is to stop iterating.

var theList = result.ToList();

for( int i = 0; i < result.Count; i++ )
{
    total += result[i];
}

Better solution:

total = result.Sum();

Upvotes: 1

Related Questions