Linq Lambda与查询语法性能

Ehs*_*jad 13 c# linq performance lambda linq-to-objects

我今天在我的项目中看到了一个linq查询语法,它以List这种方式从特定条件的项目开始计算:

int temp =  (from A in pTasks 
             where A.StatusID == (int)BusinessRule.TaskStatus.Pending     
             select A).ToList().Count();
Run Code Online (Sandbox Code Playgroud)

我想通过写它来重构它,就像使用Count()更易读,我认为它会表现得更好,所以我写道:

int UnassignedCount = pTasks.Count(x => x.StatusID == (int)BusinessRule.TaskStatus.Pending);
Run Code Online (Sandbox Code Playgroud)

但是,当我通过将StopWatchlambda表达式使用的时间进行检查时,总是比查询synax更多:

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();
Run Code Online (Sandbox Code Playgroud)

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

Far*_*yev 18

我模拟了你的情况.是的,这些查询的执行时间之间存在差异.但是,这种差异的原因不是查询的语法.如果您使用了方法或查询语法,则无关紧要.这两个产生同样的结果,因为查询表达式被翻译成他们的lambda表达式,他们正在编译之前.

但是,如果您已经注意到两个查询完全不相同.您的第二个查询将在编译之前转换为它的lambda语法(您可以删除ToList()来自查询,因为它是多余的):

pTasks.Where(x => x.StatusID == (int)BusinessRule.TaskStatus.Pending).Count();
Run Code Online (Sandbox Code Playgroud)

现在我们在lambda语法中有两个Linq查询.我上面提到的那个和这个:

pTasks.Count(x => x.StatusID == (int)BusinessRule.TaskStatus.Pending);
Run Code Online (Sandbox Code Playgroud)

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

让我们找到答案:
通过回顾这些,我们可以理解这种差异的原因:
- .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;
}
Run Code Online (Sandbox Code Playgroud)

这是Where(this IEnumerable<TSource> source, Func<TSource, bool> 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);
}
Run Code Online (Sandbox Code Playgroud)

让我们关注Where()实施.WhereListIterator()如果您的集合是List,它将返回,但Count()只会迭代源.而在我看来,他们已经取得了一些加快实施WhereListIterator.在此之后,我们调用Count()方法,该方法不接受谓词作为输入,只会迭代过滤集合.


关于加快实施的速度WhereListIterator:

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

从书中CLR via C#:

当callvirt IL指令用于调用虚拟实例方法时,CLR发现用于进行调用的对象的实际类型,然后以多态方式调用该方法.为了确定类型,用于进行调用的变量不能为null.换句话说,在编译此调用时,JIT编译器会生成验证变量值不为null的代码.如果为null,则callvirt指令会导致CLR抛出NullReferenceException.这种额外的检查意味着callvirt IL指令的执行速度比调用指令稍慢.

  • 我认为这是一个很好的调查,但我认为你应该把`int Count <TSource>(这个IEnumerable <TSource>源,Func <TSource,bool>谓词)`比作`int Count <TSource>(这个IEnumerable <TSource >来源)` (2认同)

rea*_*art 5

就像法哈德说,实施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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)