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

sti*_*k81 18 .net c# linq resharper performance

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

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

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

private Ninja FindNinjaById(int ninjaId)
{
    return Ninjas.FirstOrDefault(ninja => ninja.Id == ninjaId);
}
Run Code Online (Sandbox Code Playgroud)

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

tva*_*son 18

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

  • 纯粹的Linq无法替换的一个scneario-each当你需要对迭代的每个对象执行一些操作时.在这种情况下,我通常会创建一个Linq查询,它涵盖了我的"过滤器"逻辑,然后有一个更简单的for-each循环,对每个结果执行操作. (4认同)
  • @Chris同意了,虽然你也可以考虑简单地写一个扩展来执行动作.但是,一般来说,既然LINQ是*query*,我宁愿它没有副作用. (3认同)

Ste*_*ven 15

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

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

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

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

当性能是一个问题,也尝试使用forforeach.

再一次,我喜欢LINQ,我不记得因为性能而决定不使用LINQ(对象)查询.所以,不要做任何过早的优化.首先从最易读的解决方案开始,然后在需要时进行优化.所以型材,异型材和型材.

  • "...委托调用比方法调用慢......" - 只是一个微不足道的数量.分析时甚至看不出差异. (2认同)
  • @ Code4life:注意这个`ForEach()`方法是`List <T>`的实例方法.因此,它并没有真正针对'收藏'进行优化; 它针对`List <T>`进行了优化.但是,是的,它非常快:-)干杯. (2认同)

Ily*_*kov 7

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

Ninjas.FirstOrDefault(ninja => ninja.Id == ninjaId)
Run Code Online (Sandbox Code Playgroud)

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

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

如果你多次调用FindNinjaById,你将添加这个可能是重要的性能打击.当然,衡量它.

如果用(等效的)替换它

Ninjas.Where(ninja => ninja.Id == ninjaId).FirstOrDefault()
Run Code Online (Sandbox Code Playgroud)

它添加5.为迭代器创建状态机("Where"正在产生函数)

  • @callum,如果连续调用该函数数百万次,这并不是无关紧要的命中。确实,我们确实进行了测量。 (2认同)

Fin*_*las 6

轮廓


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

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

  • 这就是我在算法课上告诉他们的.当我可以测试它并看哪一个更快时,为什么还要烦恼所有这些O-notation的东西.;-)说真的,我认为你可以通过简单地分析代码来了解算法,看看算法属于哪个类.如果你可以使用你的代码在nlogn中执行它而LINQ将使用n**2,那么你真的不需要对它进行分析以告诉它在一般情况下哪个更快.当然,如果n非常小,那么可能并不重要,您可以使用最易读的解决方案. (3认同)

Rob*_*sor 5

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

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

拿resharper的建议:)


Jul*_*anR 5

轶事:当我刚开始了解C#3.0和LINQ时,我还处于“当您有锤子时,一切看起来都像钉子”的阶段。作为一项学校作业,我应该编写一个四/四连接的连续游戏作为对抗搜索算法的练习。我在整个程序中都使用LINQ。在一个特定的情况下,我需要找到一个将游戏放置在特定列中的行。LINQ查询的完美用例!事实证明这确实很慢。但是,LINQ并不是问题,问题在于我一直在寻找。我通过保留一个查找表来优化此效果:一个整数数组,其中包含游戏板每一列的行号,并在插入游戏部件时更新该表。不用说,这快得多了。

经验教训:首先优化您的算法,像LINQ这样的高级构造实际上可能使它更容易。

话虽如此,创建所有这些代表需要一定的成本。另一方面,利用LINQ的惰性特性也可以带来性能优势。如果您手动遍历一个集合,则几乎被迫创建中间体List<>,而使用LINQ,您基本上可以流式传输结果。