Подтвердить что ты не робот

С#: Any() vs Count() для пустого списка

A question, опубликованный ранее, заставил меня задуматься. Если бы Any() и Count() выполнялись аналогично при использовании в пустом списке?

Как описано здесь, оба должны пройти те же шаги GetEnumerator()/MoveNext()/Dispose().

Я проверил это, используя быструю программу на LINQPad:

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() непосредственно, поскольку значение будет использовано, поэтому вопрос.

4b9b3361

Ответ 1

Метод Count() оптимизирован для типа ICollection<T>, поэтому шаблон GetEnumerator()/MoveNext()/Dispose() не используется.

list.Count();

Переводится на

((ICollection)list).Count;

В то время как Any() должен создать счетчик. Таким образом, метод Count() выполняется быстрее.

Здесь приведены эталоны для 4 разных экземпляров IEnumerable. MyEmpty выглядит как IEnumerable<T> MyEmpty<T>() { yield break; }

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<int>() is ICollection<int>, потому что это массив, а массивы не подходят с расширением Count(), потому что приведение в ICollection<int> не является тривиальным.

В любом случае, для самодельного пустого IEnumerable мы можем видеть, что мы ожидали, что Count() медленнее, чем Any(), из-за накладных расходов на тестирование, если IEnumerable является ICollection.

Полный тест:

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; }
}