2

I faced a rather stupid performance issue in my code. After a small investigation, i have found that AsQueryable method i used to cast my generic list slows down the code up to 8000 times. So the the question is, why is that? Here is the example

class Program
{
    static void Main(string[] args)
    {
        var c = new ContainerTest();
        c.FillList();

        var s = Environment.TickCount;
        for (int i = 0; i < 10000; ++i)
        {
            c.TestLinq(true);
        }
        var e = Environment.TickCount;
        Console.WriteLine("TestLinq AsQueryable - {0}", e - s);

        s = Environment.TickCount;
        for (int i = 0; i < 10000; ++i)
        {
            c.TestLinq(false);
        }
        e = Environment.TickCount;
        Console.WriteLine("TestLinq as List - {0}", e - s);

        Console.WriteLine("Press enter to finish");
        Console.ReadLine();
    }
}

class ContainerTest
{
    private readonly List<int> _list = new List<int>();
    private IQueryable<int> _q; 

    public void FillList()
    {
        _list.Clear();
        for (int i = 0; i < 10; ++i)
        {
            _list.Add(i);
        }
        _q = _list.AsQueryable();
    }

    public Tuple<int, int> TestLinq(bool useAsQ)
    {
        var upperBorder = useAsQ ? _q.FirstOrDefault(i => i > 7) : _list.FirstOrDefault(i => i > 7);
        var lowerBorder = useAsQ ? _q.TakeWhile(i => i < 7).LastOrDefault() : _list.TakeWhile(i => i < 7).LastOrDefault();            

        return new Tuple<int, int>(upperBorder, lowerBorder);
    }
}

UPD As i understand, i have to avoid AsQueryable method as much as possible(if it's not in the line of inheritance of the container), because i'll get immediately performance issue

"and avoid the moor in those hours of darkness when the powers of evil are exalted"

Dmitry Savin
  • 21
  • 1
  • 3
  • you know that both test doesn't give the same result – Fredou Oct 14 '11 at 16:58
  • Ugh, thanks. You've found a bug:) – Dmitry Savin Oct 17 '11 at 14:16
  • 1
    Why are you calling AsQueryable on this collection? That is not appropriate at all for this use. It's causing your LINQ methods afterward to expect to be building a different kind of expression tree, not meant for simply collections, but for "queryable" things... like a database table. – Andrew Barber Oct 17 '11 at 14:54
  • Because initially i wanted to use it with BindingList, and i found my performance issue. But for LIst it shouldn't affect performance, since List _is_ IQueryable, so even if i call AsQueryable() it has to return list as IQueryable directly -> linq has to be executed as expression tree -> fast. But it's not – Dmitry Savin Oct 17 '11 at 14:56
  • Oh. List is not IQueryable, my bad. – Dmitry Savin Oct 17 '11 at 15:08
  • Exactly; `List` is not `IQueryable`. The implementations of the standard query operators are very different between the two. If you don't need `IQueryable`, then don't call `AsQueryable`. It's a different beast entirely. – Andrew Barber Oct 17 '11 at 16:06

3 Answers3

1

Just faced the same issue.

The thing is that IQueryable<T> takes Expression<Func<T, Bool>> as parameter for filtering in Where()/FirstOrDefault() calls - as opposed of just the Func<T, Bool> pre-compiled delegate taken in simple IEnumerable's correspondent methods.

That means there will be a compile phase to transform the Expression into a delegate. And this costs quite a lot.

Now you need that in a loop (just I did)? You'll get in some trouble...


PS: It seems .NET Core/.NET 5 improves this significantly. Unfortunately, our projects are not there yet...

J.Hudler
  • 1,238
  • 9
  • 26
0

at least use LINQ with List too

manual implementation will always be faster than LINQ

EDIT

you know that both test doesn't give the same result

Fredou
  • 19,848
  • 10
  • 58
  • 113
  • 2
    Performance difference using LINQ over foreach is marginal. It is nowhere *near* an 8000 times difference. (frankly it's nowhere near a 2 times difference) – Kirk Woll Oct 14 '11 at 16:25
  • @kirk woll, both test doesn't give result and don't use same implementation – Fredou Oct 14 '11 at 16:57
  • Ok, try TestLinq method only, one time with just List, second time with List.AsQuerable – Dmitry Savin Oct 17 '11 at 14:06
0

Because AsQueryable returns an IQueryable, which has a completely different set of extension methods for the LINQ standard query operators from the one intended for things like List.

Queryable collections are meant to have a backing store of an RDBMS or something similar, and you are building a different, more complex code expression tree when you call IQueryable.FirstOrDefault() as opposed to List<>.FirstOrDefault().

Andrew Barber
  • 39,603
  • 20
  • 94
  • 123