LINQ中Single(predicate) 和 Where(predicate).Single()到底谁快?

咨询区

  • Jonathon Reinhart

请问 someEnumerable.Single(predicate); 和 someEnumerable.Where(predicate).Single(); 到底哪一个执行的更快?

毕竟前者写法更短,更简洁,我个人感觉它就是 someEnumerable.Where(predicate).Single(); 的快捷写法,毕竟 ReSharper 也是这么建议的。

LINQ中Single(predicate) 和 Where(predicate).Single()到底谁快?

.

回答区

  • Greg Gum

要想判断快慢,最好的办法就是做 基准测试,下面是我的测试代码:

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() 大概比  someEnumerable.Single(predicate); 快两倍,我执行了两遍来排除掉是否有缓存因素的存在。

  1. 10000 calls to Single(predicate) took 7938 ms.

  2. 10000 calls to Where(predicate).Single() took 3795 ms.

  3. 10000 calls to Single(predicate) took 8132 ms.

  4. 10000 calls to Where(predicate).Single() took 4318 ms.

  • ken

我在想 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) 并没有处理这种情况。

点评区

其实用 基准测试 和 查看源码 都是一个很好的方式。