Reputation: 6679
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
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
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
Reputation: 35716
Because ElementAt
is iterating through the IEnumerable
every time you call it. IEnumerable
s are not indexed so ElementAt
must be implemented using GetEnumerator()
.
Why not do
total = result.Sum();
Upvotes: 6
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
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
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
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