【问题标题】:LINQ Lambda vs Query Syntax PerformanceLINQ Lambda 与查询语法性能
【发布时间】:2015-02-18 06:01:27
【问题描述】:

我今天在我的项目中看到了一个 LINQ 查询语法,它计算来自 List 的具有特定条件的项目,如下所示:

int temp = (from A in pTasks 
            where A.StatusID == (int)BusinessRule.TaskStatus.Pending     
            select A).ToList().Count();

我想通过使用Count(Func) 重写它来重构它,使其更具可读性。我认为这在性能方面也会很好,所以我写道:

int UnassignedCount = pTasks.Count(x => x.StatusID == (int)BusinessRule.TaskStatus.Pending);

但是当我使用StopWatch检查时,lambda表达式经过的时间总是比查询语法多:

Stopwatch s = new Stopwatch();
s.Start();
int UnassignedCount = pTasks.Count(x => x.StatusID == (int)BusinessRule.TaskStatus.Pending);
s.Stop();
Stopwatch s2 = new Stopwatch();
s2.Start();
int temp = (from A in pTasks 
            where A.StatusID == (int)BusinessRule.TaskStatus.Pending
            select A).ToList().Count();
s2.Stop();

有人可以解释为什么会这样吗?

【问题讨论】:

  • 您是否更改了这些查询的执行顺序?结果又是一样的吗?
  • 在这些之前您是否进行了 JIT 预热?
  • 与@FarhadJabiyev 相同的问题。 pTasks 下还有什么来源?这是一个 SQL 数据库,还是只是 Linq-to-objects?
  • 它是对象的 linq
  • @FarhadJabiyev 通过更改顺序也得到相同的结果

标签: c# performance linq lambda linq-to-objects


【解决方案1】:

我已经模拟了你的情况。是的,这些查询的执行时间存在差异。但是,这种差异的原因不是查询的语法。您是否使用了方法或查询语法并不重要。两者产生相同的结果,因为查询表达式在编译之前被翻译成它们的 lambda 表达式

但是,如果您注意到这两个查询根本不一样。您的第二个查询将在编译之前转换为它的 lambda 语法(您可以删除 ToList() 来自查询,因为它是多余的):

pTasks.Where(x => x.StatusID == (int)BusinessRule.TaskStatus.Pending).Count();

现在我们有两个 lambda 语法的 Linq 查询。我上面说过的那个和这个:

pTasks.Count(x => x.StatusID == (int)BusinessRule.TaskStatus.Pending);

现在的问题是:
为什么这两个查询的执行时间会有差异?

让我们找到答案:
我们可以通过回顾这些来了解这种差异的原因:
- .Where(this IEnumerable<TSource> source, Func<TSource, bool> predicate).Count(this IEnumerable<TSource> source)

-Count(this IEnumerable<TSource> source, Func<TSource, bool> predicate);

这里是Count(this IEnumerable<TSource> source, Func<TSource, bool> predicate)的实现:

public static int Count<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
    if (source == null) throw Error.ArgumentNull("source");
    if (predicate == null) throw Error.ArgumentNull("predicate");
    int count = 0;
    foreach (TSource element in source) {
        checked {
            if (predicate(element)) count++;
        }
    }
    return count;
}

这里是Where(this IEnumerable&lt;TSource&gt; source, Func&lt;TSource, bool&gt; predicate)

public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
    if (source == null) 
        throw Error.ArgumentNull("source");
    if (predicate == null) 
        throw Error.ArgumentNull("predicate");
    if (source is Iterator<TSource>) 
        return ((Iterator<TSource>)source).Where(predicate);
    if (source is TSource[]) 
        return new WhereArrayIterator<TSource>((TSource[])source, predicate);
    if (source is List<TSource>) 
        return new WhereListIterator<TSource>((List<TSource>)source, predicate);
    return new WhereEnumerableIterator<TSource>(source, predicate);
}

让我们关注Where() 的实现。如果您的集合是 List,它将返回 WhereListIterator(),但 Count() 只会遍历源代码。 在我看来,他们在WhereListIteratorimplementation 中取得了一些加速。在此之后,我们调用Count() 方法,该方法不接受谓词作为输入,只会在过滤后的集合上进行迭代。


关于WhereListIterator 的执行速度:

我在 SO:LINQ performance Count vs Where and Count 中找到了this 问题。你可以在那里阅读@Matthew Watson answer。他解释了这两个查询之间的性能差异。结果是: Where 迭代器避免了间接虚表调用,而是直接调用迭代器方法。 正如您在该答案中看到的那样,将发出call 指令而不是callvirt。而且,callvirtcall 慢:

来自bookCLR via C#

使用callvirt IL指令调用虚拟实例时 方法中,CLR 发现正在使用的对象的实际类型 进行调用,然后以多态方式调用该方法。为了 确定类型,用于进行调用的变量不得 为空。换句话说,当编译这个调用时,JIT 编译器 生成验证变量值不为空的代码。如果 它为空,callvirt 指令导致 CLR 抛出一个 空引用异常。 这个额外的检查意味着 callvirt IL 指令的执行速度比调用稍慢 说明。

【讨论】:

  • 我认为这是一项很好的调查,但我认为您应该将 int Count&lt;TSource&gt;(this IEnumerable&lt;TSource&gt; source, Func&lt;TSource, bool&gt; predicate)int Count&lt;TSource&gt;(this IEnumerable&lt;TSource&gt; source) 进行比较。
  • @Enigmativity 是的,毕竟Count 它只会在过滤后的集合上迭代。我已经更新了我的答案。
【解决方案2】:

就像 Farhad 所说,Where(x).Count()Count(x) 的实现各不相同。第一个实例化了一个额外的迭代器,在我的电脑上花费大约 30.000 滴答声(不管集合大小)

另外,ToList 不是免费的。它分配内存。这需要时间。在我的电脑上,它大约使执行时间加倍。 (所以线性依赖于集合大小)

此外,调试需要启动时间。因此,很难一次性准确地衡量性能。我会推荐一个像这个例子这样的循环。然后,忽略第一组结果。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            var pTasks = Task.GetTasks();
            for (int i = 0; i < 5; i++)
            {

                var s1 = Stopwatch.StartNew();
                var count1 = pTasks.Count(x => x.StatusID == (int) BusinessRule.TaskStatus.Pending);
                s1.Stop();
                Console.WriteLine(s1.ElapsedTicks);

                var s2 = Stopwatch.StartNew();
                var count2 =
                    (
                        from A in pTasks
                        where A.StatusID == (int) BusinessRule.TaskStatus.Pending
                        select A
                        ).ToList().Count();
                s2.Stop();
                Console.WriteLine(s2.ElapsedTicks);

                var s3 = Stopwatch.StartNew();
                var count3 = pTasks.Where(x => x.StatusID == (int) BusinessRule.TaskStatus.Pending).Count();
                s3.Stop();
                Console.WriteLine(s3.ElapsedTicks);


                var s4 = Stopwatch.StartNew();
                var count4 =
                    (
                        from A in pTasks
                        where A.StatusID == (int) BusinessRule.TaskStatus.Pending
                        select A
                        ).Count();
                s4.Stop();
                Console.WriteLine(s4.ElapsedTicks);

                var s5 = Stopwatch.StartNew();
                var count5 = pTasks.Count(x => x.StatusID == (int) BusinessRule.TaskStatus.Pending);
                s5.Stop();
                Console.WriteLine(s5.ElapsedTicks);
                Console.WriteLine();
            }
            Console.ReadLine();
        }
    }

    public class Task
    {
        public static IEnumerable<Task> GetTasks()
        {
            for (int i = 0; i < 10000000; i++)
            {
                yield return new Task { StatusID = i % 3 };
            }
        }

        public int StatusID { get; set; }
    }

    public class BusinessRule
    {
        public enum TaskStatus
        {
            Pending,
            Other
        }
    }
}

【讨论】:

    猜你喜欢
    • 2010-11-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-04
    • 2011-04-16
    • 1970-01-01
    • 2013-01-13
    • 1970-01-01
    相关资源
    最近更新 更多