【问题标题】:Performance of Linq query by type按类型划分的 Linq 查询性能
【发布时间】:2012-11-16 13:20:57
【问题描述】:

工作中出现了一个讨论:

我们有一个具有 IList 的类。 Fact 是一个抽象基类,有几个具体的子类(PopulationFact、GdpFact 等)。

最初我们会以这种方式查询给定的事实,即按类型:

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

然而,现在有人提出了我们是否应该引入 FactType 枚举来代替的问题

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

之所以提出该建议,是因为它据说速度更快。我承认我没有编写任何测试来尝试辨别性能差异,但我有两个问题:

1) 这样的“查询类型”本身就不好吗?
2) 考虑到事实是强类型的,添加 FactType 枚举不是多余的吗?

更新 澄清一下,这是 LINQ to objects 和 GdpFact:Fact。

更新 2 我们使用当前的典型数据(4 个事实)进行了测量,结果如下:

枚举查找:0.29660000000000003 毫秒 查找类型:0.24530000000000002 毫秒

所以在这种情况下类型查找更快!我会仔细选择我接受的答案。

【问题讨论】:

  • 是 linq to objects 还是 linq to SQL?或者,更一般地说,您是在调用 Enumerable.FirstOrDefault 还是 Queryable.FirstOrDefault?另外,请注意,这两种方法并不等效。如果你有从 GdpFact 派生的事实类型,x is GdpFact 将是这些类型的实例,但x.FactType == FactType.Gdp 可能是错误的。
  • 那么,FactsIList<Fact> 是什么类型的GdpFact:Fact?这似乎合乎逻辑,但你没有说。
  • .Facts.OfType<GdpFact>().FirstOrDefault(x => x.Year == 2011) 也是一个选项。
  • 无论任何人声称它都应该通过测试来支持
  • 您考虑过.OfType() 方法吗?不确定这是如何实现的,因此对性能有影响,但它允许很好的声明性代码msdn.microsoft.com/en-us/library/bb360913.aspx

标签: c# .net performance linq


【解决方案1】:

我做了一个测试,我的 1000000 次迭代的结果大约是

ByCast 166ms
ByType 84ms
ByEnum 98ms

所以enum 实际上是多余的,而且速度较慢,但​​幅度不大。这应该不足为奇,类型系统是 .Net 框架的基础。

测试代码抄录如下,有错误请见谅

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);
    }
}

这个question 似乎很相关。

【讨论】:

    【解决方案2】:

    所有类型的性能相关问题都严格依赖于具体的应用程序上下文,因此对于您的具体案例,此处提供的答案可能部分正确/错误.

    记住这一点:

    检查枚举值应该比检查类型要快得多,因为在第一种情况下,您只需检查相等 2 整数(枚举值)。

    但是在对象中引入了一个额外的字段,必须跟踪它以具有正确的值(单元测试),在第二种情况下您不需要,因为CLR 关心正确的类型初始化。

    我认为你最好根据相关数据量来分析你的想法,你的应用程序通常会运行,并且会为你提出正确的想法

    【讨论】:

    • 谢谢。好答案。毫无疑问,上下文很重要。在我们的例子中,一次只需要处理几个 Facts(不超过 10-20 个)。
    • 在这一点上,我认为这两种情况之间没有相关(显着)差异。顺便说一下,测量。
    【解决方案3】:

    我认为你原来的方法很好。为此目的提供了“is”关键字。 MSDN 不鼓励使用“是”。使用枚举似乎是过度工程。我们应该尽量保持代码简单。在大多数情况下,代码行数越少越好。

    【讨论】:

    • 谢谢。我认为这也太过分了——并且怀疑“社区”也会有这种感觉……在 SO 上发帖是一种构建“弹药”的方式 :-)
    • 我知道你的意思 :) 祝你讨论顺利 :)
    【解决方案4】:

    这可能是寻找问题的解决方案吗?

    我认为在基本类型中同时包含具体子类型和枚举可能会混淆您的设计。你可以想象后来有人出现并编写了一个新的具体类,但没有意识到他们也需要添加到枚举中......

    除非您发现您在性能方面存在特定问题,否则我会倾向于优先考虑清晰度。因此,如果您需要不同的具体类(我假设您这样做了,因为这是您开始编码的方式),那么我会坚持使用您的类型而不是移动到枚举。

    【讨论】:

    • 嗯,这是我的“担忧”之一。我认为有一个类型枚举没有多大意义:)
    【解决方案5】:

    检查枚举值可能比运行时类型检查更快,但是...

    1. 这是一种微优化 - 您不太可能注意到实际场景中的性能差异。
    2. 它让事情变得更复杂,更复杂意味着更容易出错。
      例如,是什么阻止您或您的一位同事不小心做出这样的事情?

      public class PopulationFact : Fact
      {
          public FactType FactType = FactType.GdpFact;  // should be PopulationFact
      }
      

    我会坚持类型检查。实际上有 a built-in LINQ method 会为你做这件事:

    .Facts.FirstOrDefault(x => x.Year == 2011).OfType<GdpFact>()
    

    【讨论】:

      猜你喜欢
      • 2013-08-04
      • 2012-11-03
      • 2015-08-17
      • 2023-01-31
      • 2012-07-09
      • 1970-01-01
      • 1970-01-01
      • 2012-05-16
      • 2015-04-11
      相关资源
      最近更新 更多