关于.net:C#:Any()vs Count()表示空列表

C#: Any() vs Count() for an empty list

早前的一个问题让我思考。当用于空列表时,Any()Count()的性能是否相似?

正如这里所解释的,这两个步骤都应该与GetEnumerator()/MoveNext()/Dispose()的步骤相同。

我用linqpad上的快速程序测试了这个问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static void Main()
 {
    var list = new List<int>();

    Stopwatch stopwatch = new Stopwatch();
    stopwatch.Start();

    for (int i = 0; i < 10000; i++)
        list.Any();

    stopwatch.Stop();
    Console.WriteLine("Time elapsed for Any()   : {0}", stopwatch.Elapsed);


    stopwatch = new Stopwatch();
    stopwatch.Start();

    for (int i = 0; i < 10000; i++)
        list.Count();

    stopwatch.Stop();
    Console.WriteLine("Time elapsed for Count(): {0}", stopwatch.Elapsed);
}

总体结果似乎表明,在这种情况下,Count()的速度更快。为什么会这样?

我不确定我是否有正确的基准,如果没有,我会感激任何修正。

编辑:我明白这在语义上会更有意义。我在问题中发布的第一个链接显示了这样一种情况,即直接使用Count()是有意义的,因为将使用该值,因此出现了问题。


Count()方法针对ICollection类型进行了优化,因此不使用模式GetEnumerator()/MoveNext()/Dispose()

1
list.Count();

转换为

1
((ICollection)list).Count;

Any()必须建立一个枚举器。因此,Count()方法更快。

这里有一个针对4种不同情况的基准1(5)。MyEmpty看起来像IEnumerable MyEmpty() { yield break; }

1
2
3
4
5
6
7
iterations : 100000000

Function                      Any()     Count()
new List<int>()               4.310     2.252
Enumerable.Empty<int>()       3.623     6.975
new int[0]                    3.960     7.036
MyEmpty<int>()                5.631     7.194

正如casperone在评论中所说,Enumerable.Empty() is ICollection,因为它是一个数组,而数组不适合Count()扩展,因为对ICollection的强制转换并不简单。

不管怎样,对于自制的空的IEnumerable,我们可以看到我们所期望的,Count()Any()慢,因为如果IEnumerableICollection,测试的开销。

完成基准:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Program
{
    public const long Iterations = (long)1e8;

    static void Main()
    {
        var results = new Dictionary<string, Tuple<TimeSpan, TimeSpan>>();
        results.Add("new List<int>()", Benchmark(new List<int>(), Iterations));
        results.Add("Enumerable.Empty<int>()", Benchmark(Enumerable.Empty<int>(), Iterations));
        results.Add("new int[0]", Benchmark(new int[0], Iterations));
        results.Add("MyEmpty<int>()", Benchmark(MyEmpty<int>(), Iterations));

        Console.WriteLine("Function".PadRight(30) +"Any()".PadRight(10) +"Count()");
        foreach (var result in results)
        {
            Console.WriteLine("{0}{1}{2}", result.Key.PadRight(30), Math.Round(result.Value.Item1.TotalSeconds, 3).ToString().PadRight(10), Math.Round(result.Value.Item2.TotalSeconds, 3));
        }
        Console.ReadLine();
    }

    public static Tuple<TimeSpan, TimeSpan> Benchmark(IEnumerable<int> source, long iterations)
    {
        var anyWatch = new Stopwatch();
        anyWatch.Start();
        for (long i = 0; i < iterations; i++) source.Any();
        anyWatch.Stop();

        var countWatch = new Stopwatch();
        countWatch.Start();
        for (long i = 0; i < iterations; i++) source.Count();
        countWatch.Stop();

        return new Tuple<TimeSpan, TimeSpan>(anyWatch.Elapsed, countWatch.Elapsed);
    }

    public static IEnumerable<T> MyEmpty<T>() { yield break; }
}