Strange slowness when repeating a Linq result

Studying a recent Linq question, I noticed that the algorithm looks pretty slow. Digging deeper, I noticed that this is not linq code, but the result of the result subsequently took a lot of time. (Kudos to Marc Gravel btw for kicking Slickest Linq, which I have seen yet.)

code:

DateTime dt = DateTime.Now; Console.WriteLine("Alg Start " + dt.Second + "." + dt.Millisecond); var qry = from l in Enumerable.Range(100000, 999999) let s = l.ToString() let sReversed = new string(s.Reverse().ToArray()) from i in Enumerable.Range(3, 9) let t = (l * i).ToString() where t == sReversed select new { l, i }; dt = DateTime.Now; Console.WriteLine("Alg End " + dt.Second + "." + dt.Millisecond); foreach (var row in qry) Console.WriteLine("{0} x {1} = {2}", row.l, row.i, row.l * row.i); dt = DateTime.Now; Console.WriteLine("Disp End " + dt.Second + "." + dt.Millisecond); 

Output:

 Alg Start 20.257 Alg End 20.270 109989 x 9 = 989901 219978 x 4 = 879912 1099989 x 9 = 9899901 Disp End 31.322 

.13 seconds to calculate and more than 11 to display?!? What is the reason for this?

+3
source share
6 answers

The reason is because the request is not actually executed until you list it. In LINQ for objects, it simply creates a group of delegates that are invoked when they step through the counter. If you must add ToList () in the request in order to materialize it, you will see that the time spent on the transfer will be changed to the setting and off-screen.

+5
source

The reason linq query is executed quickly is because nothing is evaluated at the definition point, since linq uses delayed execution, i.e. no "real" work is done until you start listing the results.

+5
source

with many linq providers, "alg start" to "alt end" is simply parsed - the actual expressions are not evaluated until you actually start listing the result. Thus, the actual creation of the variable "qry" is fast (just setting up an enumerable that will actually execute the logic in the query), but enumerating through it is slower.

+3
source

LINQ code creates a query object from a query expression, which does not take much time. Actually executed only in foreach.

By the way, you should not use DateTime.Now to synchronize performance, but the stopwatch class, since it is much more accurate.

+3
source

The query is not actually computed until you go to it. Until then, it's like an SQL statement awaiting execution.

+3
source

This question makes gross coercion; LINQ is actually very convenient in such cases - I discussed it here: Brute force (but lazy)

Just to expand some of the previous answers:

LINQ is usually created around deferred execution, which means that nothing happens before the result starts to repeat. This is usually done through an iterator block; consider the difference between the two:

 static IEnumerable<T> Where(this IEnumerable<T> data, Func<T,bool> predicate) { foreach(T item in data) { if(predicate(item)) yield return item; } } 

and

 static IEnumerable<T> Where(this IEnumerable<T> data, Func<T,bool> predicate) { var list = new List<T>(); foreach(T item in data) { if(predicate(item)) list.Add(item); } return list; } 

The difference is that the second version does all the work when you call Where , returning one result, where - how the second (through the magic of iterator blocks) only works when the enumerator calls MoveNext() . Iterator blocks are discussed in more detail in the free chapter of the C # <6> example in depth .

In general, the advantage of this is that it makes queries complex, especially important for database-based queries, but in the same way as for normal operation.

Note that even with iterator blocks, second exists; buffering. Consider Reverse() - no matter how you do it, to change the sequence, you first need to find the end of the sequence. Now consider that not all sequences end! Compare this with Where , Skip , Take , etc. - which can filter rows without buffering (simply by deleting elements).

A good example of using this in infinite sequence is this Fibonacci question , where we can use the unbuffered, deferred approach:

  foreach (long i in Fibonacci().Take(10)) { Console.WriteLine(i); } 

Without delayed execution, this will never end.

+2
source

All Articles