使用LINQ“无处不在”时的性能问题?

时间:2010-04-20 11:24:22

标签: c# .net linq performance resharper

升级到ReSharper5后,它为我提供了更多有关代码改进的有用提示。我现在到处看到的一个是用LINQ查询替换foreach语句的提示。举个例子:

private Ninja FindNinjaById(int ninjaId)
{
    foreach (var ninja in Ninjas)
    {
        if (ninja.Id == ninjaId)
            return ninja;
    }
    return null;
}

建议使用LINQ替换为以下内容:

private Ninja FindNinjaById(int ninjaId)
{
    return Ninjas.FirstOrDefault(ninja => ninja.Id == ninjaId);
}

这看起来很好,我确信在替换这个foreach时性能没有问题。但这是我应该做的一般事情吗?或者我可能遇到所有这些LINQ查询的性能问题?

9 个答案:

答案 0 :(得分:18)

您需要了解LINQ查询将在“幕后”进行的操作,并将其与运行代码进行比较,然后才能知道是否应该更改它。一般来说,我并不是说你需要知道将要生成的确切代码,但你需要知道它将如何执行操作的基本思路。在你的例子中,我猜测LINQ基本上和你的代码一样工作,因为LINQ语句更紧凑和描述性,我更喜欢它。但有时候,LINQ可能不是理想的选择,尽管可能并不多。一般来说,我认为几乎任何循环结构都可以被等效的LINQ结构替换。

答案 1 :(得分:15)

首先让我说我喜欢LINQ的表现力并且一直使用它而没有任何问题。

但是在性能方面存在一些差异。通常它们足够小,可以忽略,但在应用程序的关键路径中,有时可能需要优化它们。

以下是您应该注意的一组差异,这可能与性能有关:

  • LINQ过度使用委托调用,委托调用比方法调用慢一点(当然慢于内联代码)。
  • 委托是对象内的方法指针。需要创建该对象。
  • LINQ运算符通常返回一个允许循环遍历集合的新对象(迭代器)。链式LINQ运算符因此创建了多个新对象。
  • 当你的内部循环使用来自外部的对象(称为闭包)时,它们也必须包装在对象中(需要创建)。
  • 许多LINQ运算符调用集合上的GetEnumerator方法来迭代它。调用GetEnumerator通常可以确保创建另一个对象。
  • 使用IEnumerator界面迭代集合。接口调用比普通方法调用慢一点。
  • IEnumerator对象通常需要处理,或者至少必须调用Dispose

如果需要考虑性能问题,请尝试使用for而不是foreach

同样,我喜欢LINQ和我不记得因为性能而决定不使用LINQ (对象)查询。所以,不要做任何过早的优化。首先从最易读的解决方案开始,然后在需要时进行优化。所以个人资料,个人资料和个人资料

答案 2 :(得分:7)

我们发现性能问题的一件事是创建大量lambdas并迭代小集合。转换后的样本会发生什么?

Ninjas.FirstOrDefault(ninja => ninja.Id == ninjaId)

首先,创建(生成的)闭包类型的新实例。托管堆中的新实例,有些适用于GC。 其次,从该闭包中的方法创建新的委托实例。 然后调用方法FirstOrDefault。它能做什么? 它迭代集合(与原始代码相同)并调用委托。

所以基本上,你在这里添加了4件事: 1.创建闭包 2.创建委托 3.通过代表打电话 4.收集闭包和委托

如果您多次调用FindNinjaById,您将添加此内容可能是重要的性能打击。当然,衡量它。

如果用(等效)

替换它
Ninjas.Where(ninja => ninja.Id == ninjaId).FirstOrDefault()

它补充说 5.为迭代器创建状态机(“Where”正在产生函数)

答案 3 :(得分:6)

资料


确切知道的唯一方法是剖析。是的,某些查询可能会更慢。但是当你看看ReSharper在这里取代了什么时,它本质上是一样的,以不同的方式完成。 ninjas循环,每个Id都被检查。如果有的话,你可以说这个重构归结为可读性。您觉得哪两个更容易阅读?

更大的数据集肯定会产生更大的影响,但正如我所说,简介。这是确定此类增强是否会产生负面影响的唯一方法。

答案 4 :(得分:5)

我们已经构建了大量的应用程序,LINQ遍布各处。永远不会让我们放慢脚步。

编写非常慢的LINQ查询是完全可能的,但是修复简单的LINQ语句要比/ if / for / return算法更容易。

采取resharper的建议:)

答案 5 :(得分:5)

一则轶事:当我刚刚了解C#3.0和LINQ时,我仍然在“当你有一把锤子,一切看起来像一个钉子”阶段。作为一项学校作业,我应该写一个连接四/四连接游戏作为对抗性搜索算法的练习。我在整个程序中使用了LINQ。在一个特殊的情况下,如果我把它放在一个特定的列中,我需要找到一个游戏块落下的行。 LINQ查询的完美用例!事实证明这很慢。但是,LINQ不是问题,问题是我正在搜索开始。我通过保存一个查找表来优化它:一个整数数组,包含游戏板每列的行号,在插入游戏时更新该表。毋庸置疑,这要快得多。

经验教训:首先优化算法,LINQ等高级构造实际上可以更容易。

尽管如此,创建所有代表的成本确实很高。另一方面,利用LINQ的懒惰性质也可以带来性能优势。如果你手动循环一个集合,你几乎被迫创建中间List<>,而使用LINQ,你基本上流式传输结果。

答案 6 :(得分:3)

以上内容完全相同。

只要您正确使用LINQ查询,就不会遇到性能问题。如果您正确使用它,由于创建LINQ的人员的技能,它更可能更快。

创建自己的唯一好处就是如果你想要完全控制,或者LINQ没有提供你需要的东西,或者你想要更好的调试能力。

答案 7 :(得分:3)

关于LINQ查询的一个很酷的事情是它使dead simple转换为并行查询。取决于你正在做什么,它可能会或可能不会更快(一如既往,简介),但它仍然非常整洁。

答案 8 :(得分:3)

为了增加我自己使用LINQ的经验,其中性能确实很重要 - 使用Monotouch - 差异仍然微不足道。

你在3GS iPhone上“残疾”到大约46mb的RAM和620mhz的ARM处理器。不可否认,代码是AOT编译的,但即使在模拟器上它是JIT并且经历了一系列间接,对于1000个对象的集合,差异是十分之一毫秒。

除了Windows Mobile,您还必须担心性能成本 - 而不是在四核8gb服务器上运行的大型ASP.NET应用程序或具有双重分数的桌面。这种情况的一个例外是大型对象集,尽管可能无论如何都会延迟加载,并且初始查询任务将在数据库服务器上执行。

这在Stackoverflow上有点陈词滥调,但使用更短的更易读的代码,直到100毫秒确实很重要。