簡體   English   中英

哪個更快:單個(謂詞)或Where(謂詞)。單個()

[英]Which is faster: Single(predicate) or Where(predicate).Single()

這個答案引起的討論讓我很好奇。 哪個更快:

someEnumerable.Single(predicate);

要么

someEnumerable.Where(predicate).Single();

畢竟,第一個更短,更簡潔,似乎是專門建造的。

甚至ReSharper建議前者:

在此輸入圖像描述

我在上一篇文章中爭論說,它們在功能上是相同的,並且應該具有非常相似的運行時。

LINQ到對象

沒有什么像這樣的基准回答像這樣的問題:

(更新)

class Program
{
    const int N = 10000;
    volatile private static int s_val;

    static void DoTest(IEnumerable<int> data, int[] selectors) {
        Stopwatch s;

        // Using .Single(predicate)
        s = Stopwatch.StartNew();
        foreach (var t in selectors) {
            s_val = data.Single(x => x == t);
        }
        s.Stop();
        Console.WriteLine("   {0} calls to Single(predicate) took {1} ms.",
            selectors.Length, s.ElapsedMilliseconds);

        // Using .Where(predicate).Single()
        s = Stopwatch.StartNew();
        foreach (int t in selectors) {
            s_val = data.Where(x => x == t).Single();
        }
        s.Stop();
        Console.WriteLine("   {0} calls to Where(predicate).Single() took {1} ms.",
            selectors.Length, s.ElapsedMilliseconds);
    }


    public static void Main(string[] args) {
        var R = new Random();
        var selectors = Enumerable.Range(0, N).Select(_ => R.Next(0, N)).ToArray();

        Console.WriteLine("Using IEnumerable<int>  (Enumerable.Range())");
        DoTest(Enumerable.Range(0, 10 * N), selectors);

        Console.WriteLine("Using int[]");
        DoTest(Enumerable.Range(0, 10*N).ToArray(), selectors);

        Console.WriteLine("Using List<int>");
        DoTest(Enumerable.Range(0, 10 * N).ToList(), selectors);

        Console.ReadKey();
    }
}

有點令人震驚的是, .Where(predicate).Single()獲勝約兩倍。 我甚至兩次運行兩個案例以確保緩存等不是一個因素。

1) 10000 calls to Single(predicate) took 7938 ms.
1) 10000 calls to Where(predicate).Single() took 3795 ms.
2) 10000 calls to Single(predicate) took 8132 ms.
2) 10000 calls to Where(predicate).Single() took 4318 ms.

更新結果:

Using IEnumerable<int>  (Enumerable.Range())
   10000 calls to Single(predicate) took 7838 ms.
   10000 calls to Where(predicate).Single() took 8104 ms.
Using int[]
   10000 calls to Single(predicate) took 8859 ms.
   10000 calls to Where(predicate).Single() took 2970 ms.
Using List<int>
   10000 calls to Single(predicate) took 9523 ms.
   10000 calls to Where(predicate).Single() took 3781 ms.

Where(predicate).Single()會比Single(predicate)更快

編輯:你會期望Single()Single(predicate)以類似的方式編碼,但事實並非如此。 一旦找到另一個元素, Single()完成,但后者會找到所有令人滿意的元素。

酒店的其他點(原來的答案) - Where確實為不同類型的集合類型的特殊優化,而其他的方法,如FirstSingleCount不采取集合類型的優勢。

所以Where(predicate).Single()能夠做一些Single(predicate)沒有的優化

基於Where(predicate).Single()Single(predicate)的實際實現,似乎前者實際上是懶惰的,而后者總是迭代整個IEnumerable Single()既返回枚舉的唯一元素,又測試枚舉是否為none或具有多個值,這可以通過最多詢問枚舉的下兩個元素來實現。 Single(predicate)當前是以一種方式實現的,它需要遍歷整個枚舉,以確認謂詞對於一個且僅一個元素是否為true ,從而確定性能(和功能,見下文)的差異。

雖然它們在功能上看起來相同,但有些情況下,不僅性能,而且實際功能完全不同,即無限枚舉,

public IEnumerable<int> InfiniteEnumeration()
{
    while (true)
    {
        yield return 1;
    }
}

如果使用這兩種方法運行此功能,則可以正確完成; 另一個......我們可能要等。

var singleUsingWhere = InfiniteEnumeration().Where(value => value != 0).Single();
var singleUsingSingle = InfiniteEnumeration().Single(value => value != 0);

奇怪的是,微軟決定以這種方式實現Single(predicate) ......甚至Jon Skeet設法修復了這種疏忽

Linq-for-objects Single存在設計缺陷,意味着:

  1. 這是毫無意義地保持匹配數量的計數,而不是找到匹配,然后如果找到另一個匹配則拋出。
  2. 它一直持續到序列結束,即使在第三場比賽之后。
  3. 它可以拋出OverflowException ; 它不太可能,但它根本就是一個錯誤。

https://connect.microsoft.com/VisualStudio/feedback/details/810457/public-static-tsource-single-tsource-this-ienumerable-tsource-source-func-tsource-bool-predicate-doesnt-throw-immediately-上第二匹配結果#

這使得它在0或1匹配的情況下略微變慢(當然只有第二個是非錯誤情況),並且在多於1個匹配(錯誤情況)的情況下慢得多。

與其他Linq提供商一樣,這取決於; 它往往大致相同,但對於給定的提供者來說,完全有可能對一個或另一個提供者效率較低,而另一個給定的提供者則相反。

[編輯:.NET Core不再是這種情況,上面的描述不再適用。 這使得對單個調用.Single(pred)效率比.Where(pred).Single()

我認為這是蘋果與橘子的情況。

我們必須考慮Single(predicate)的當前實現與以下實現的不同之處:

public static TSource Single<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
    return Where(source, predicate).Single();
}

Where的實現返回一個Enumerable.Iterator ,它看起來像是識別在不同線程中的同一個迭代器上MoveNext時發生的競爭條件。

來自ILSpy:

switch (this.state)
{
case 1:
    this.enumerator = this.source.GetEnumerator();
    this.state = 2;
    break;
case 2:
    break;
default:
    return false;
}

Single(predicate)First(predicate)的當前實現不處理這種情況。

我正在努力思考這在現實場景中意味着什么 ,但我猜測“bug”還沒有得到修復,因為在某些多線程場景中行為會被改變。

暫無
暫無

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

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