Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Performance of Linq query by type

A discussion has come up at work:

We've got a class that has an IList. Fact is an abstract base class, and there are several concrete subclasses (PopulationFact, GdpFact, etc).

Originally we'd query for a given fact in this fashion, i.e. by type:

.Facts.FirstOrDefault(x => x.Year == 2011 && x is GdpFact)

Now, however, the question's been raised whether we should introduce a FactType enum instead to do

.Facts.FirstOrDefault(x => x.Year == 2011 && x.FactType == FactType.Gdp)

The suggestion has been raised because it is supposedly faster. I'll admit that I've not written any tests to try and discern the difference in performance, but I have two questions:

1) Is 'querying on type' like this inherently bad?
2) Isn't adding a FactType enum just superfluous anyway, given that facts are strongly typed?

UPDATE To clarify, this is LINQ to objects and GdpFact:Fact.

UPDATE 2 We've measured using current typical data (4 facts) and the results are in:

lookup on enum: 0.29660000000000003 milliseconds lookup on type: 0.24530000000000002 milliseconds

So type lookup is faster in this context! I will choose my accepted answer carefully.

like image 278
Øyvind Avatar asked Nov 28 '12 16:11

Øyvind


People also ask

Is LINQ fast or slow?

It is slightly slower LINQ syntax is typically less efficient than a foreach loop. It's good to be aware of any performance tradeoff that might occur when you use LINQ to improve the readability of your code. And if you'd like to measure the performance difference, you can use a tool like BenchmarkDotNet to do so.

Which is faster LINQ or SQL?

Stored procedures are faster as compared to LINQ query since they have a predictable execution plan and can take the full advantage of SQL features. Hence, when a stored procedure is being executed next time, the database used the cached execution plan to execute that stored procedure.

Is LINQ faster than for loop?

No, LINQ iterators are not and will never be faster than foreach . Also, List.


1 Answers

I've done a test, my results for 1000000 iterations are approximately

ByCast 166ms
ByType 84ms
ByEnum 98ms

So the enum is in fact superfluous and slower but, not by much. This should not be too suprising, the type system is fundamental to the .Net Framework.

Test code transcribed below, apologies for errata

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

class Program
{
    private enum TypeOfFact
    {
        Gdp,
        Other
    }

    private abstract class Fact
    {
        public virtual int Year { get; set; }
        public abstract TypeOfFact FactType { get; }
    }

    private class GdpFact : Fact
    {
        public override TypeOfFact FactType
        {
            get { return TypeOfFact.Gdp; }
        }
    }

    private class OtherFact : Fact
    {
        public override TypeOfFact FactType
        {
            get { return TypeOfFact.Other; }
        }
    }

    static void Main()
    {
        Ilist<Fact> facts = new List<Fact>
            {
                new GdpFact { Year = 2010 },
                new OtherFact { Year = 2010 },
                new GdpFact { Year = 2009 },
                new OtherFact { Year = 2009 },
                new GdpFact { Year = 2011 },
                new OtherFact { Year = 2011 },
            };

        const int interations = 1000000;

        var funcs = new List<Func<IList<Fact>, Fact>>
            {
                ByList,
                ByType,
                ByEnum
            };

        // Warmup
        foreach (var func in funcs)
        {
           Measure(5, func, facts);
        }

        // Results
        foreach (var result in funcs.Select(f => new
            {
                Description = f.Method.Name,
                Ms = Measure(iterations, f, facts)
            }))
        {
            Console.WriteLine(
                "{0} time = {1}ms",
                result.Description,
                result.Ms);
        }
    }

    private static long Measure(
        int iterations,
        Func<IList<Fact>, Fact> func,
        IList<Fact> facts)
    {
        var stopwatch = new Stopwatch();
        stopwatch.Start();
        for (var i = 0; i < iterations; i++)
        {
            func.Invoke(facts);
        }

        stopwatch.Stop();
        return stopwatch.ElapsedMilliseconds;
    }

    private static Fact ByType(IList<Fact> facts)
    {
        return facts.FirstOrDefault(f =>
            f.Year == 2011 && f is GdpFact);
    }

    private static Fact ByEnum(IList<Fact> facts)
    {
        return facts.FirstOrDefault(f =>
            f.Year == 2011 && f.FactType == TypeOfFact.Gdp);
    }

    private static Fact ByCast(IList<Fact> facts)
    {
        return facts.OfType<GdpFact>()
            .FirstOrDefault(f => f.Year == 2011);
    }
}

This question seems relevant.

like image 80
Jodrell Avatar answered Oct 01 '22 09:10

Jodrell