哪个更快:单个(谓词)或Where(谓词)。单个()

时间:2014-01-17 19:58:49

标签: c# .net linq

this answer引发的讨论令我很好奇。哪个更快:

someEnumerable.Single(predicate);

someEnumerable.Where(predicate).Single();

毕竟,第一个更短,更简洁,似乎是专门建造的。

即使ReSharper建议前者:

enter image description here

我在上一篇文章中争论说,它们功能相同,应该具有非常相似的运行时间。

5 个答案:

答案 0 :(得分:33)

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.

答案 1 :(得分:8)

Where(predicate).Single()会比Single(predicate)

更快

修改:您希望以类似的方式对Single()Single(predicate)进行编码,但事实并非如此。 Single()一找到另一个元素就会结束,但后者会找到所有令人满意的元素。

其他兴趣点(原始答案) - Where针对不同类型的集合类型进行了特殊优化,而其他方法(如FirstSingleCount则没有利用集合的类型。

因此Where(predicate).Single()能够进行Single(predicate)

的一些优化

答案 2 :(得分:4)

根据Where(predicate).Single()Single(predicate)的实际实现,似乎前实际上是懒惰,而后者总是迭代整个IEnumerableSingle()都返回枚举的唯一元素,但也测试枚举是否没有或有多个值,这可以通过最多询问枚举的下两个元素来实现。 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 managed to fixed that oversight

答案 3 :(得分:4)

Linq for for objects Single中存在设计缺陷,意味着:

  1. 毫无意义地记录了比赛的数量,而不是找到一场比赛,然后如果找到另一场比赛就扔掉。
  2. 即使在第三场比赛之后,它一直持续到序列结束。
  3. 它可以抛出OverflowException;它不太可能,但它根本就是一个错误。
  4. https://connect.microsoft.com/VisualStudio/feedback/details/810457/public-static-tsource-single-tsource-this-ienumerable-tsource-source-func-tsource-bool-predicate-doesnt-throw-immediately-on-second-matching-result#

    这使得它在0或1匹配的情况下略微变慢(当然,只有第二个是非错误情况),并且在多于1个匹配的情况下(错误情况)慢得多。

    与其他Linq提供商一样,这取决于;它往往大致相同,但是对于给定的提供者来说,完全有可能对一个或另一个提供者效率较低而另一个提供者则相反。

    [编辑:.NET Core不再是这种情况,上面的描述不再适用。这使得对.Single(pred)的单次调用效率略高于.Where(pred).Single()。]

答案 4 :(得分:1)

我认为这是苹果与橘子的情况。

我们必须考虑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”还没有得到修复,因为行为会在某些多方面被改变 - 线程场景。