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

时间:2010-09-22 14:00:01

标签: c# .net linq performance

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

花了1443ms来做1000000000比较非LINQ 花了4944ms做了1000000000与LINQ比较 (慢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;
        }
    }
}

花了1443ms做1000000000比较非LINQ。

LINQ代码:

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

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

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

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

17 个答案:

答案 0 :(得分:232)

  

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

没有。如果不够快,应该避免。 不够快根本就不一样了!

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

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

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

答案 1 :(得分:152)

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

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

LINQ 极大地有助于处理数据的代码的表现力......并且编写表现良好的代码并不难,只要你花时间了解LINQ就可以了。

如果有人告诉不使用LINQ(尤其是LINQ to Objects)来感受感知速度的原因,我会笑他们的脸。如果他们提出了一个特定的瓶颈并说:“我们可以通过在这种情况下不使用LINQ来加快速度,这就是证据”那么这是一个非常不同的事情。

答案 2 :(得分: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.");
    }
}

不要担心Profiler类的内部。它只是在循环中运行Action并使用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

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

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

  • 使用最合适的方法(ICollection<string>.Contains)与未经优化的数据结构(List<string>)比使用{{1}天真地大约高50% } loop。

  • 使用优化的数据结构(foreach)可以在性能方面彻底击败任何其他方法。

我不知道你从哪里得到243%。我的猜测是它与所有的铸造有关。如果您使用的是HashSet<string>,那么您不仅使用未经优化的数据结构,而且还使用的是过时的数据结构。

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

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

底线是:

  

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

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

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

答案 3 :(得分:16)

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

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

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

答案 4 :(得分:12)

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

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

答案 5 :(得分:7)

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

如果要通过名称属性对MyLinqTestClass1对象进行哈希处理,可以击败其中任何一个。在那些之间,如果可以按名称对它们进行排序,然后进行二进制搜索。实际上,我们不需要为此存储MyLinqTestClass1对象,我们只需要存储名称。

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

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

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

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

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

在有意义的地方使用LINQ。

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

答案 6 :(得分:4)

也许linq很慢,但是使用linq我可以非常简单地并行化我的代码。

像这样:

lst1.Cast<MyLinqTestClass1>().AsParallel().Any(item => item.Name == "9999");

如何并行化循环?

答案 7 :(得分:4)

对我来说,这听起来像是你正在签订合同,雇主要么不了解LINQ,要么不了解系统的性能瓶颈。如果您正在使用GUI编写应用程序,则使用LINQ对性能的轻微影响可以忽略不计。在典型的GUI / Web应用程序中,内存中调用占所有等待时间的不到1%。您或者更确切地说是您的雇主正试图优化1%。这真的有益吗?

但是,如果您正在编写一个科学或面向数学的应用程序,只有很少的磁盘或数据库访问权限,那么我同意LINQ不是可行的方法。

顺便说一下,不需要演员阵容。以下功能与您的第一次测试相同:

       for (int i = 0; i < 10000; i++)
            isInGroup = lst1.Any(item => item.Name == "9999");

当我使用包含10,000个MyLinqTestClass1对象的测试列表运行它时,原始版本在2.79秒内运行,修订版在3.43秒内运行。对可能占用CPU时间不到1%的操作节省30%并不能很好地利用你的时间。

答案 8 :(得分:3)

这是一个有趣的观察,因为你提到nHibernate由于LINQ缓慢而变慢。如果您正在使用LINQ to SQL(或等效的nHibernate),那么您的LINQ代码将转换为SQL服务器上的EXISTS查询,因为您的循环代码必须首先获取所有行,然后迭代它们。现在,您可以轻松编写此类测试,以便循环代码读取所有10K运行的所有数据(单个数据库查找),但LINQ代码实际执行10K SQL查询。这可能会显示出现实中不存在的循环版本的巨大速度优势。实际上,单个EXISTS查询每次都会超过表扫描和循环 - 即使您没有查询列的索引(如果经常进行此查询,您可能会这样做)。

我不是说你的测试就是这种情况 - 我们没有足够的代码可以看 - 但它可能是。也可能是LINQ to Objects确实存在性能差异,但可能根本不会转换为LINQ to SQL。您需要知道您正在测量的内容以及它对您的实际需求的适用程度。

答案 9 :(得分:3)

“我曾被告知[由谁?]因为.net linq如此缓慢[为了什么?]我们不应该使用它”

根据我的经验,根据什么技术,图书馆或语言做出的决定只能用于某人 曾经告诉你的内容,这是一个坏主意。

首先,信息来自您信任的来源吗?如果没有,你可能会犯这个(也许是未知的)人做出设计决定的错误。其次,这些信息今天仍然有用吗?但是,基于您的简单而不是非常现实的基准测试,您已经得出结论,LINQ比手动执行相同操作要慢。问自己的一个自然问题是:这个代码性能是否至关重要?这个代码的性能是否会被其他因素限制,而不是LINQ查询的执行速度 - 想想数据库查询,等待I / O等等?

以下是我喜欢的工作方式:

  1. 确定要解决的问题,并根据您已了解的要求和限制编写最简单的功能完整解决方案
  2. 确定您的实施是否真正符合要求(是否足够快?资源消耗是否保持在可接受的水平?)。
  3. 如果确实如此,那就完成了。如果没有,请寻找优化和优化解决方案的方法,直到它通过#2的测试。这是可能需要考虑放弃某些东西的地方,因为它太慢了。也许。但是,很可能瓶颈并不是你所期望的那样。
  4. 对我来说,这个简单的方法只有一个目的:最大化我的工作效率最小化我花费在改进已经完全足够的代码上的时间。

    是的,当您发现原始解决方案不再削减它时,可能会出现这一天。或者它可能不会。如果确实如此,那就处理它。我建议你尽量避免浪费时间来解决假设的(未来)问题。

答案 10 :(得分:1)

是的,你是对的。在LINQ中编写慢速代码很容易。其他的也是对的:在没有LINQ的情况下在C#中编写慢速代码很容易。

我在C中编写了与你相同的循环,并且运行速度提高了几毫秒。我从中得出的结论是C#本身很慢。

与LINQ-&gt;循环扩展一样,在C中,执行相同操作的代码行数将超过5倍,这使得编写速度变慢,难以阅读,更容易出现错误,以及更难以找到并修复它们,但如果每十亿次迭代节省几毫秒是很重要的,那通常就是它需要的。

答案 11 :(得分:0)

我宁愿说你应该避免努力编写最有效的代码,除非它是强制性的。

答案 12 :(得分:0)

正如您所演示的那样,可以编写性能优于LINQ代码的非LINQ代码。但反过来也是可能的。鉴于LINQ可以提供的维护优势,您可能会考虑默认为LINQ,因为您不太可能遇到任何可归因于LINQ的性能瓶颈。

也就是说,有些情况下LINQ无法正常工作。例如,如果要导入大量数据,您可能会发现执行单个插入的操作比以批量XML将数据发送到SQL Server要慢。在这个例子中,LINQ插入并不比非LINQ插入更快,而不是为批量数据导入执行单独的SQL插入。

答案 13 :(得分:0)

  

鉴于LINQ很慢,那么PLINQ也会慢,NHibernate LINQ会很慢,所以不应该使用LINQ语句中的任何一种。

这是一种不同的背景,但却截然不同。当你谈论数据访问操作时,整个10亿次操作的1.4秒和5秒是无关紧要的。

答案 14 :(得分:0)

您的测试用例有点偏斜。 ANY操作符将开始枚举您的结果,如果找到并退出,则在第一个实例上返回true。尝试使用简单的字符串列表来查看结果。要回答关于避免LINQ的问题,您应该真正转向使用LINQ。除了编译时间检查之外,它还使代码在执行复杂查询时更容易阅读。此外,您不需要在示例中使用Cast运算符。

string compareMe = "Success";
string notEqual = "Not Success";

List<string> headOfList = new List<string>();
List<string> midOfList = new List<string>();
List<string> endOfList = new List<string>();

//Create a list of 999,999 items
List<string> masterList = new List<string>();
masterList.AddRange(Enumerable.Repeat(notEqual, 999999));

//put the true case at the head of the list
headOfList.Add(compareMe);
headOfList.AddRange(masterList);

//insert the true case in the middle of the list
midOfList.AddRange(masterList);
midOfList.Insert(masterList.Count/2, compareMe);

//insert the true case at the tail of the list
endOfList.AddRange(masterList);
endOfList.Add(compareMe);


Stopwatch stopWatch = new Stopwatch();

stopWatch.Start();
headOfList.Any(p=>p == compareMe);
stopWatch.ElapsedMilliseconds.Dump();
stopWatch.Reset();

stopWatch.Start();
midOfList.Any(p=>p == compareMe);
stopWatch.ElapsedMilliseconds.Dump();
stopWatch.Reset();

stopWatch.Start();
endOfList.Any(p=>p == compareMe);
stopWatch.ElapsedMilliseconds.Dump();
stopWatch.Stop();

答案 15 :(得分:0)

类型转换当然会降低你的代码速度。如果您非常关心,至少使用强类型的IEnumerable进行比较。我自己尝试尽可能使用LINQ。它使您的代码更简洁。这并不是必须担心代码的必要细节。 LINQ是一个功能概念,这意味着你将阐明你想要发生的事情而不用担心如何。

答案 16 :(得分:-17)

避免Linq的理由要好上百倍。

关于Linq的讨论引用了一些名字:

QUOTE1

“例如,这有效:

var a = new { x = 1, y = 2 };
a = new { x = 1, y = 3 };

但这不起作用:

var a = new { x = 1, y = 2 };
a = new { x = 1, y = 2147483649 };

返回:Error 1 Cannot implicitly convert type 'AnonymousType#1' to 'AnonymousType#2'

但这有效:

var a = new { x = 1, y = 2147483648 };
a = new { x = 1, y = 2147483649 };

编译时:

var a = new { x = 1, y = 2 };

x和y组件的类型被任意声明为32位有符号整数,它是平台所具有的众多整数类型之一,没有任何特殊之处。

但还有更多。例如,这有效:

double x = 1.0;
x = 1;

但这不起作用:

var a = new { x = 1.0, y = 0 }; 
a = new { x = 1, y = 0 };

数字转换规则不适用于此类类型。正如您所看到的,每个细节都有优雅。“

QUOTE2

“看来,'AnonymousType#1'和'AnonymousType#2'不是同义词 - 它们命名不同的类型。而{ x = 1, y = 2 }{ y = 2, x = 1 }是这两种类型的表达,它们不仅分别表示不同的值,还表示不同类型的值。

所以,我是偏执狂是对的。现在我的妄想症进一步延伸,我不得不问LinQ对以下比较做了什么:

new { x = 1, y = 2 } == new { x = 1, y = 2 }

结果为false,因为这是指针比较。

但结果是:

(new { x = 1, y = 2 }).Equals(new { x = 1, y = 2 })

是真的。

结果:

(new { x = 1, y = 2 }).Equals(new { y = 2, x = 1 })

(new { x = 1, y = 2 }).Equals(new { a = 1, b = 2 })

是假的。“

QUOTE3

“更新是面向记录的:-O

我同意这是有问题的,并且源于LINQ的面向序列的性质。

对我来说,这是一个节目。如果我不得不使用SQL进行更新,为什么要打扰LinQ?

LinQ对象的优化是不存在的。

没有任何代数优化也没有自动表达式重写。许多人不想将LinQ用于对象,因为它们会失去很多性能。查询的执行方式与编写查询的方式相同。“