I have two classes that perform date date range data fetching for particular days.
public class IterationLookup
{
private IList-
Sometimes LINQ appears slower because the generation of delegates in a loop (especially a non-obvious loop over method calls) can add time. Instead, you may want to consider moving your finder out of the class to make it more generic (like your key selector is on construction):
public class LinqLookup<TItem, TKey>
{
private IList<Item> items = null;
public IterationLookup(IEnumerable<TItem> items, Func<TItem, TKey> keySelector)
{
this.items = items.OrderByDescending(keySelector).ToList();
}
public TItem GetItem(Func<TItem, TKey> selector)
{
return this.items.FirstOrDefault(selector);
}
}
Since you don't use a lambda in your iterative code, this can be a bit of a difference since it has to create the delegate on each pass through the loop. Usually, this time is insignificant for every-day coding, and the time to invoke the delegate is no more expensive than other method calls, it's just the delegate creation in a tight loop that can add that little bit of extra time.
In this case, since the delegate never changes for the class, you can create it outside of the code you are looping through and it would be more efficient.
Update:
Actually, even without any optimization, compiling in release mode on my machine I do not see the 5x difference. I just performed 1,000,000 lookups on an Item
that only has a DateTime
field, with 5,000 items in the list. Of course, my data, etc, are different, but you can see the times are actually really close when you abstract out the delegate:
iterative : 14279 ms, 0.014279 ms/call
linq w opt : 17400 ms, 0.0174 ms/call
These time differences are very minor and worth the readability and maintainability improvements of using LINQ. I don't see the 5x difference though, which leads me to believe there's something we're not seeing in your test harness.
Further to Gabe's answer, I can confirm that the difference appears to be caused by the cost of re-constructing the delegate for every call to GetPointData
.
If I add a single line to the GetPointData
method in your IterationRangeLookupSingle
class then it slows right down to the same crawling pace as LinqRangeLookupSingle
. Try it:
// in IterationRangeLookupSingle<TItem, TKey>
public TItem GetPointData(DateTime point)
{
// just a single line, this delegate is never used
Func<TItem, bool> dummy = i => i.IsWithinRange(point);
// the rest of the method remains exactly the same as before
// ...
}
(I'm not sure why the compiler and/or jitter can't just ignore the superfluous delegate that I added above. Obviously, the delegate is necessary in your LinqRangeLookupSingle
class.)
One possible workaround is to compose the predicate in LinqRangeLookupSingle
so that point
is passed to it as an argument. This means that the delegate only needs to be constructed once, not every time the GetPointData
method is called. For example, the following change will speed up the LINQ version so that it's pretty much comparable with the foreach
version:
// in LinqRangeLookupSingle<TItem, TKey>
public TItem GetPointData(DateTime point)
{
Func<DateTime, Func<TItem, bool>> builder = x => y => y.IsWithinRange(x);
Func<TItem, bool> predicate = builder(point);
return this.items.FirstOrDefault(predicate);
}
Assume you have a loop like this:
for (int counter = 0; counter < 1000000; counter++)
{
// execute this 1M times and time it
DateTime day = GetRandomDay();
items.FirstOrDefault(i => i.IsWithinRange(day));
}
This loop will create 1,000,000 lambda objects in order for the i.IsWithinRange
call to access day
. After each lambda creation, the delegate that calls i.IsWithinRange
gets invoked on average 1,000,000 * items.Length
/ 2 times. Both of those factors do not exist in your foreach
loop, which is why the explicit loop is faster.