Является ли значение Linq Count() более быстрым или медленным, чем List.Count или Array.Length?

Является ли метод Linq Count() быстрее или медленнее, чем List<>.Count или Array.Length?

Ответ 1

В целом медленнее. Общее число LINQ - это операция O(N), в то время как List.Count и Array.Length гарантированы как O(1).

Однако в некоторых случаях LINQ будет особым случаем с параметром IEnumerable<T> путем литья определенных типов интерфейсов, таких как IList<T> или ICollection<T>. Затем он будет использовать этот метод Count для выполнения фактической операции Count(). Таким образом, он вернется к O(1). Но вы по-прежнему оплачиваете незначительные накладные расходы на вызов трансляции и интерфейса.

Ответ 2

Метод Enumerable.Count() проверяет ICollection<T>, используя .Count - поэтому в случае массивов и списков он не намного более неэффективен (просто дополнительный уровень косвенности).

Ответ 3

Марк имеет правильный ответ, но дьявол находится в деталях.

На моей машине:

  • Для массивов .Length примерно в 100 раз быстрее .Count()
  • Для списков .Count примерно в 10 раз быстрее .Count() - Примечание: я ожидал бы подобную производительность из всех Коллекций, которые реализуют IList<T>

Массивы начинают медленнее, так как .Length включает только одну операцию,.Count на массивах включает слой косвенности. Итак.Совокупность массивов начинается с 10x медленнее (на моей машине), что может быть одной из причин, по которой интерфейс реализован явно. Представьте, что если у вас есть объект с двумя общедоступными свойствами,.Count и .Length. Оба делают то же самое, но .Count в 10 раз медленнее.

Конечно, это не имеет большого значения, поскольку вам нужно будет подсчитывать свои массивы и перечислять миллионы раз в секунду, чтобы почувствовать удар производительности.

Код:

    static void TimeAction(string description, int times, Action func) {
        var watch = new Stopwatch();
        watch.Start();
        for (int i = 0; i < times; i++) {
            func();
        }
        watch.Stop();
        Console.Write(description);
        Console.WriteLine(" Time Elapsed {0} ms", watch.ElapsedMilliseconds);
    } 

    static void Main(string[] args) {
        var array = Enumerable.Range(0, 10000000).ToArray();
        var list = Enumerable.Range(0, 10000000).ToArray().ToList();

        // jit
        TimeAction("Ignore and jit", 1 ,() =>
        {
            var junk = array.Length;
            var junk2 = list.Count;
            array.Count();
            list.Count();
        });


        TimeAction("Array Length", 1000000, () => {
            var tmp1 = array.Length;
        });

        TimeAction("Array Count()", 1000000, () =>
        {
            var tmp2 = array.Count();
        });

        TimeAction("Array Length through cast", 1000000, () =>
        {
            var tmp3 = (array as ICollection<int>).Count;
        });


        TimeAction("List Count", 1000000, () =>
        {
            var tmp1 = list.Count;
        });

        TimeAction("List Count()", 1000000, () =>
        {
            var tmp2 = list.Count();
        });

        Console.ReadKey();
    }

Результаты:

Array Length Time Elapsed 3 ms
Array Count() Time Elapsed 264 ms
Array Length through cast Time Elapsed 16 ms
List Count Time Elapsed 3 ms
List Count() Time Elapsed 18 ms

Ответ 4

Я считаю, что если вы вызовете Linq.Count() на ICollection или IList (например, ArrayList или List), он просто вернет значение свойства Count. Таким образом, производительность будет примерно одинаковой в простых коллекциях.

Ответ 5

Я бы сказал, что это зависит от Списка. Если IQueryable является таблицей в db где-то, то Count() будет намного быстрее, потому что ему не нужно загружать все объекты. Но если список в памяти, я бы предположил, что свойство Count будет быстрее, если не о том же.

Ответ 6

Дополнительная информация - LINQ Count - разница между ее использованием и не может быть огромной - и это не обязательно должно быть слишком большим. У меня есть коллекция, образованная из linq для объектов с примерно 6500 элементами (большая.. но не огромная никакими средствами). Count() в моем случае занимает несколько секунд. Преобразование в список (или массив, что бы то ни было), тогда счет будет фактически немедленным. Наличие этого счета во внутреннем цикле означает, что воздействие может быть огромным. Граф перечисляет все. Массив и список являются "осведомленными" о своей длине и не нуждаются в их перечислении. Любые отладочные операторы (log4net для ex), ссылающиеся на этот счетчик(), также замедляют все значительно. Сделайте себе одолжение, и если вам нужно ссылаться на это, часто сохраняйте размер счета и только вызывайте его один раз в коллекции LINQ, если вы не конвертируете его в список, а затем можете ссылаться без удара производительности.

Вот краткий тест того, о чем я говорил выше. Обратите внимание, что каждый раз, когда мы вызываем Count(), размер нашей коллекции изменяется. Следовательно, происходит оценка, которая больше, чем ожидаемая операция "count". Просто то, что нужно знать:)

    
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;

    namespace LinqTest
    {
        class TestClass
        {
            public TestClass()
            {
                CreateDate = DateTime.Now;
            }
            public DateTime CreateDate;
        }

        class Program
        {

            static void Main(string[] args)
            {
                //Populate the test class
                List list = new List(1000);
                for (int i=0; i<1000; i++)
                {
                    System.Threading.Thread.Sleep(20);
                    list.Add(new TestClass());
                    if(i%100==0)
                    { 
                        Console.WriteLine(i.ToString() +  " items added");
                    }
                }

                //now query for items 
                var newList = list.Where(o=> o.CreateDate.AddSeconds(5)> DateTime.Now);
                while (newList.Count() > 0)
                {
                    //Note - are actual count keeps decreasing.. showing our 'execute' is running every time we call count.
                    Console.WriteLine(newList.Count());
                    System.Threading.Thread.Sleep(500);
                }
            }
        }
    }