问题 Linq Lambda与查询语法性能


我今天看到了我的项目中的linq查询语法 List 特定条件的项目:

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

我想通过写它就像使用它来重构它 Count() 为了使更多的可读性和我认为它将是性能明智也好,所以我写道:

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

但是当我通过推杆检查 StopWatch lambda表达式所经历的时间总是超过查询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();

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


5032
2018-02-18 06:01


起源

您是否更改了这些查询的执行顺序?结果是否相同? - Farhad Jabiyev
你之前做过JIT热身吗? - Yuval Itzchakov
和@FarhadJabiyev一样的问题。还有什么来源 pTasks?这是一个SQL数据库,还是只是Linq-to-objects? - Jeppe Stig Nielsen
它是对象的灵感 - Ehsan Sajjad
@FarhadJabiyev通过改变顺序也同样的结果 - Ehsan Sajjad


答案:


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

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


13
2018-02-18 06:44



我认为这是一个很好的调查,但我认为你应该进行比较 int Count<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) 至 int Count<TSource>(this IEnumerable<TSource> source)。 - Enigmativity
@Enigmativity是的,在结束之后 Count 它只会在过滤后的集合上进行迭代。我已经更新了我的答案。 - Farhad Jabiyev


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

此外,ToList不是免费的。它分配内存。这需要时间。在我的电脑上,它大约是执行时间的两倍。 (所以线性相关op集合大小)

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

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
        }
    }
}

3
2018-02-18 17:48