应该避免LINQ,因为它很慢吗?

use*_*095 57 .net c# linq performance

我有人被告知,因为.net linq是如此之慢,我们不应该使用它,并且想知道其他人是否得出了相同的结论,例如:

花了1443ms做了1000000000比较非LINQ.
花了4944ms与LINQ做了1000000000比较.
(慢243%)

非LINQ代码:

for (int i = 0; i < 10000; i++)
{
    foreach (MyLinqTestClass1 item in lst1) //100000 items in the list
    {
        if (item.Name == "9999")
        {
            isInGroup = true;
            break;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

花了1443ms做了1000000000比较非LINQ.

LINQ代码:

for (int i = 0; i < 10000; i++)  
    isInGroup = lst1.Cast<MyLinqTestClass1>().Any(item => item.Name == "9999");  
Run Code Online (Sandbox Code Playgroud)

花了4944ms与LINQ做了1000000000比较.

我想它可以优化LINQ代码,但想到的是它很容易得到非常慢的LINQ代码,并且考虑到它不应该被使用.鉴于LINQ很慢,那么PLINQ也会很慢,NHibernate LINQ会很慢,所以不应该使用LINQ语句中的任何类型.

有没有人发现LINQ是如此之慢以至于他们希望从未使用它,或者我是否根据这样的基准做出了过于笼统的结论?

Eri*_*ert 233

Linq应该避免因为它的速度慢吗?

不,如果不够快,应该避免.而且不够快并不是完全相同的事情!

与您的客户,您的管理层和利益相关者无关.不够快是非常相关的.永远不要测量某物的速度 ; 它告诉你什么都不能用来做出商业决策.衡量客户接受的接近程度.如果可以接受,那就停止花钱让它更快; 它已经足够好了.

性能优化很昂贵.编写代码以便其他人可以阅读和维护代码是昂贵的.这些目标往往是相互对立的,所以为了负责任地花费利益相关者的钱,你必须确保你只花费宝贵的时间和精力对不够快的事情进行性能优化.

您发现了一种人为的,不切实际的基准测试情况,其中LINQ代码比编写代码的其他方式慢.我向您保证,您的客户不会关心您不切实际的基准测试的速度.他们只关心你运送给他们的程序是否太慢.我向你保证,你的管理层对此并不关心(如果他们有能力的话); 他们关心你不必要花多少钱来制作足够快速的东西,并使代码在阅读,理解和维护过程中更加昂贵.

  • @ user455095:所以你所说的是你知道的魔鬼比你不知道的魔鬼更好.哪个没问题,*如果你喜欢根据旧的说法做出商业决定*.我认为根据*经验测量*得出的*知情意见*做出商业决策通常是一个更好的主意.通过进行经验测量,你在这里走向正确的方向,这样做很好.但是你要将这种衡量标准与错误的事物进行比较.您应该衡量的是客户满意度和股东成本,而不是每次比较的微秒. (28认同)
  • @ user455095:那为什么所有的担心?今天尝试使用LINQ,*明天你会知道它是否会导致不可接受的高性能降级*.你说使用LINQ的不确定性太高了; 好吧,你不会变得更加确定,直到你花一些时间使用它,并了解什么对你有用,什么不对.如果您的问题确实是"应该避免LINQ,因为我的团队不了解如何有效地使用它?" 那是一个与你提出的问题不同的问题.我对这个问题的回答是"不,而是学习如何有效地使用它!" (16认同)
  • @ user455095:听起来你对未来的表现感到焦虑.缓解这种焦虑的方法是制定夜间自动化性能测试策略,以测量*真实*产品与*真实*客户指标的性能.这样,您每天都可以跟踪绩效数字的趋势.如果您发现性能不佳突然飙升,那么您可以检查当天的所有更改日志,并找出导致性能不佳的更改.在你不知道的情况下,解决性能问题的费用会增加. (11认同)
  • @user:如何而不是"消除可能导致性能问题的任何事情",你可以从正确且易于维护的代码开始,然后在它们出现时消除*特定的*性能问题(如果你的软件很好,这应该很容易 - 设计和精心设计)?我不想这么直率,但你基本上向世界宣布你不知道如何做你的工作.你的"问题"的一部分似乎是代码是如此"大规模" - 好吧,猜猜看,如果你有效地使用LINQ它就不会那么大. (8认同)
  • @user,我向你保证,当LINQ的执行速度比你自己编写的代码快时,会有例子.不是因为你不能编写比LINQ更快的代码,只是你没有时间这么做.你花费的所有时间都在努力达到规范,整合经常互相矛盾的业务规则,找到并消除错误等.同时,微软有一群人优化了"加入"操作.使用LINQ,您将编写更具表现力的代码,并且您将更快地编写代码.它也会表现良好. (7认同)
  • 好吧,这是你的问题.团队负责人. (4认同)

Jon*_*eet 152

你为什么用Cast<T>()?基本上,你没有给我们足够的代码来真正判断基准.

是的,您可以使用LINQ编写慢速代码.你猜怎么着?您也可以编写慢速非LINQ代码.

LINQ 极大地帮助了处理数据的代码的表现力......只要你花时间去理解LINQ,那么编写表现良好的代码就不那么难了.

如果有人告诉不要使用LINQ(LINQ尤其是到对象)的感知速度的原因,我会在自己的脸上笑.如果他们想出了一个特定的瓶颈并说:"我们可以通过在这种情况下不使用LINQ来加快速度,这就是证据"那么这是一个非常不同的问题.

  • @ user455095:不,这绝对不是一个有效的假设."慢"远不是一个精确的术语 - 我非常怀疑这是一个现实的基准. (39认同)
  • "如果有人告诉我不要使用LINQ(尤其是LINQ to Objects),因为我觉得速度快,我会笑一笑." 出于同样的原因,我实际上笑了几张脸.而且,当他们说"我看不到价值"时. (7认同)
  • "我们的应用程序需要快速,所以我想这个想法是应该避免任何可能缓慢的事情" - 这是我听过的最愚蠢的事情.字符串可能很慢.数组可能很慢.虚方法可能很慢,接口可能很慢.任何事情都可能很慢,如果你使用它错了,但这并不意味着你应该避免代码中的所有那些*到处*. (6认同)
  • 是的,我给出了答复,取出演员会有所帮助,但被告知它不是很慢但仍然很慢.我们的应用程序需要快速,所以我想这个想法是应该避免任何可能缓慢但我不确定这是否是一个有效的假设 (4认同)
  • 我唯一一次遇到因为性能原因而删除LINQ的情况是在游戏中常规实现AI.这种特殊方法在深内环中非常频繁地执行.我发现的主要影响实际上不是LINQ,而是直接通过数组索引与通过枚举器索引之间的区别(我的第一次改进尝试是直接使用foreach,这比切换到经典的for循环更不利.我只是做了这个改变,因为分析确定代码花了40%的时间在这里. (4认同)
  • +1"如果有人告诉我不要使用LINQ(特别是对象的LINQ)因为速度的原因我会嘲笑他们的脸".我希望你在我的公司Jon Skeet工作,我真的这样做!:-) (3认同)

Aar*_*ght 84

也许我错过了一些东西,但我很确定你的基准测试是关闭的.

我用以下方法测试:

  • Any扩展方法("LINQ")
  • 一个简单的foreach循环(您的"优化"方法)
  • 使用ICollection.Contains方法
  • Any使用优化数据结构的扩展方法(HashSet<T>)

这是测试代码:

class Program
{
    static void Main(string[] args)
    {
        var names = Enumerable.Range(1, 10000).Select(i => i.ToString()).ToList();
        var namesHash = new HashSet<string>(names);
        string testName = "9999";
        for (int i = 0; i < 10; i++)
        {
            Profiler.ReportRunningTimes(new Dictionary<string, Action>() 
            {
                { "Enumerable.Any", () => ExecuteContains(names, testName, ContainsAny) },
                { "ICollection.Contains", () => ExecuteContains(names, testName, ContainsCollection) },
                { "Foreach Loop", () => ExecuteContains(names, testName, ContainsLoop) },
                { "HashSet", () => ExecuteContains(namesHash, testName, ContainsCollection) }
            },
            (s, ts) => Console.WriteLine("{0, 20}: {1}", s, ts), 10000);
            Console.WriteLine();
        }
        Console.ReadLine();
    }

    static bool ContainsAny(ICollection<string> names, string name)
    {
        return names.Any(s => s == name);
    }

    static bool ContainsCollection(ICollection<string> names, string name)
    {
        return names.Contains(name);
    }

    static bool ContainsLoop(ICollection<string> names, string name)
    {
        foreach (var currentName in names)
        {
            if (currentName == name)
                return true;
        }
        return false;
    }

    static void ExecuteContains(ICollection<string> names, string name,
        Func<ICollection<string>, string, bool> containsFunc)
    {
        if (containsFunc(names, name))
            Trace.WriteLine("Found element in list.");
    }
}
Run Code Online (Sandbox Code Playgroud)

不要担心Profiler班上的内部.它只是Action在循环中运行并使用a Stopwatch来计时.它还确保GC.Collect()在每次测试之前调用以消除尽可能多的噪声.

结果如下:

      Enumerable.Any: 00:00:03.4228475
ICollection.Contains: 00:00:01.5884240
        Foreach Loop: 00:00:03.0360391
             HashSet: 00:00:00.0016518

      Enumerable.Any: 00:00:03.4037930
ICollection.Contains: 00:00:01.5918984
        Foreach Loop: 00:00:03.0306881
             HashSet: 00:00:00.0010133

      Enumerable.Any: 00:00:03.4148203
ICollection.Contains: 00:00:01.5855388
        Foreach Loop: 00:00:03.0279685
             HashSet: 00:00:00.0010481

      Enumerable.Any: 00:00:03.4101247
ICollection.Contains: 00:00:01.5842384
        Foreach Loop: 00:00:03.0234608
             HashSet: 00:00:00.0010258

      Enumerable.Any: 00:00:03.4018359
ICollection.Contains: 00:00:01.5902487
        Foreach Loop: 00:00:03.0312421
             HashSet: 00:00:00.0010222
Run Code Online (Sandbox Code Playgroud)

数据非常一致,并讲述了以下故事:

  • 天真地使用Any扩展方法比使用foreach循环的天使慢约9%.

  • 使用ICollection<string>.Contains具有未优化数据结构(List<string>)的最合适的方法()比使用循环的天使约50%foreach.

  • HashSet<string>在性能方面,使用优化的数据结构()完全将任何其他方法从水中吹走.

我不知道你从哪里得到243%.我的猜测是它与所有的铸造有关.如果你正在使用a,ArrayList那么你不仅使用了未经优化的数据结构,而且还使用了一种基本上过时的数据结构.

我可以预测接下来会发生什么. "是的,我知道你可以更好地优化它,但这只是比较LINQ与非LINQ性能的一个例子."

是的,但是如果你的例子中甚至不能彻底,你怎么可能期望在生产代码中这么彻底呢?

底线是这个:

您构建和设计软件的方式比您使用和何时使用的特定工具更重要.

如果你遇到了性能瓶颈 - 这与LINQ相比没有那么可能发生 - 然后解决它们.Eric对自动化性能测试的建议非常好; 这将帮助您尽早发现问题,以便您可以正确地解决问题- 而不是通过回避一个令您高出80%的工具,但恰好会导致<10%的性能损失,但通过实际调查问题并提出来有一个真正的解决方案,可以将您的性能提高2倍,10倍,或100倍或更多.

创建高性能应用程序并不是要使用正确的库.这是关于分析,做出好的设计选择,以及编写好的代码.


STW*_*STW 16

LINQ是否是现实世界的瓶颈(要么影响应用程序的整体性能还是感知性能)?

您的应用程序是否会在现实世界中的1,000,000,000条记录中执行此操作?如果是这样 - 那么你可能想考虑其他选择 - 如果没有,那就像是说"我们不能购买这款家用轿车,因为它在180+ MPH下驾驶不好".

如果它"只是慢"那么这不是一个很好的理由......通过这种推理你应该用asm/C/C++编写所有东西,而C#应该在桌面上"太慢".

  • 什么是有意义的清洗?你是那个知道你的代码在做什么以及它是如何做的人.如果LINQ执行起来很慢,那么不要使用LINQ.如果没有性能问题,那没关系,是吗? (8认同)
  • @ user455095:我很遗憾地说,你关于某些可能导致一些微不足道的性能问题的未来情况的争论不会持续存在.听听这些人的意见; 他们知道他们在说什么.Eric Lippert在构建您的C#编译器的团队中工作,以防您不知道.Jon Skeet在Google工作.这些绅士和其他人都试图告诉你一些事情.LISTE N. (4认同)

sne*_*rch 12

虽然过早的悲观化(早期)与早期优化一样糟糕,但您不应该在不考虑使用情况的情况下排除基于绝对速度的整个技术.是的,如果你正在做一些非常沉重的数字运算并且这是一个瓶颈,LINQ 可能会有问题 - 简介它.

你可以使用支持LINQ的一个论点是,尽管你可以用手写代码表现得更好,但LINQ版本可能更清晰,更容易维护 - 而且,与复杂的手动并行化相比,PLINQ具有优势.

  • +1"过早悲观化" (6认同)

Jon*_*nna 7

这种比较的问题在于它在抽象中毫无意义.

如果一个人可以通过他们的Name属性散列MyLinqTestClass1对象来开始,那么可以击败其中任何一个.在那些之间,如果可以按名称对它们进行排序,然后进行二进制搜索.实际上,我们不需要为此存储MyLinqTestClass1对象,我们只需要存储名称.

内存大小有问题吗?也许将名称存储在DAWG结构中,组合足够然后用于此检查?

设置这些数据结构的额外开销是否有意义?这是不可能的.

另一个问题是LINQ概念的另一个问题,即它的名称.MS的营销目的很好,能够说"这里有一堆很酷的新东西可以一起工作",但是当人们在进行那种将它们分开的分析时将各种东西组合在一起时不太好.你需要调用Any它基本上实现了.NET2.0中常见的可枚举过滤模式(并且.NET1.1并不为人所知,尽管写入更加尴尬意味着它仅在其效率受益的情况下使用某些情况确实很重要),你有lambda表达式,并且你在一个概念中将查询树全部组合在一起.哪个慢?

我敢打赌,这里的答案是lambda而不是使用Any,但我不打赌大量(例如项目的成功),我会测试并确定.同时,lambda表达式与IQueryable一起使用的方式可以使特别有效的代码在没有使用lambdas的情况下以相同的效率编写是非常困难的.

当LINQ擅长效率时,我们不能提高效率,因为它失败了人工基准吗?我不这么认为.

在有意义的地方使用LINQ.

在瓶颈条件下,然后离开或移到LINQ,尽管它看似合适或不合适作为优化.不要先写下难以理解的代码,因为你只会更难实现真正的优化.