簡體   English   中英

LINQ Lambda 與查詢語法性能

[英]LINQ Lambda vs Query Syntax Performance

我今天在我的項目中看到了一個 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();

有人可以解釋為什么會這樣嗎?

我已經模擬了你的情況。 是的,這些查詢的執行時間存在差異。 但是,這種差異的原因不是查詢的語法。 您是否使用過方法或查詢語法都沒有關系。 這兩個產生同樣的結果,因為查詢表達式被翻譯成他們的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<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);
}

讓我們關注Where()實現。 如果您的集合是 List,它將返回WhereListIterator() ,但Count()只會遍歷源。 在我看來,他們在WhereListIterator實現方面做了一些加速 在此之后,我們將調用Count()方法,該方法不將謂詞作為輸入,只會在過濾后的集合上進行迭代。


關於WhereListIterator實現的WhereListIterator

我在 SO: LINQ performance Count vs Where and Count 中發現了這個問題。 您可以在那里閱讀@Matthew Watson 的回答 他解釋了這兩個查詢之間的性能差異。 結果是: Where迭代器避免了間接虛表調用,而是直接調用迭代器方法。 正如您在該應答中看到的那樣,將發出call指令而不是callvirt 而且, callvirtcall慢:

從書CLR via C#

當 callvirt IL 指令用於調用虛擬實例方法時,CLR 會發現用於進行調用的對象的實際類型,然后以多態方式調用該方法。 為了確定類型,用於進行調用的變量不能為空。 換句話說,在編譯此調用時,JIT 編譯器會生成驗證變量值不為空的代碼。 如果它為 null,則 callvirt 指令會導致 CLR 拋出 NullReferenceException。 這個額外的檢查意味着 callvirt IL 指令的執行速度比 call 指令稍慢。

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM