Лучше ли вызывать ToList () или ToArray () в запросах LINQ?

519

Я часто сталкиваюсь со случаем, когда хочу проверить запрос именно там, где я его объявляю. Обычно это происходит потому, что мне нужно многократно повторять его, а вычисление обходится дорого. Например:

string raw = "...";
var lines = (from l in raw.Split('\n')
             let ll = l.Trim()
             where !string.IsNullOrEmpty(ll)
             select ll).ToList();

Это отлично работает. Но если я не собираюсь изменять результат, я мог бы также позвонить ToArray()вместоToList() .

Интересно, однако, ToArray()реализуется ли это при первом вызове ToList()и, следовательно, менее эффективно использует память, чем просто вызовToList() .

Я сумасшедший? Должен ли я просто позвонить ToArray()- безопасно и надежно, зная, что память не будет выделяться дважды?

Фрэнк Крюгер
источник
10
Если вы когда-нибудь захотите узнать, что происходит за кулисами в .NET, я очень рекомендую .NET Reflector
Дэвид Хедлунд,
32
@DavidHedlund Я рекомендую исходный код .net .
Gqqnbig
1
Я не согласен с тем, что stackoverflow.com/questions/6750447/c-toarray-performance является дубликатом этого вопроса, несмотря на наличие важных отношений. Как использование памяти (этот вопрос), так и производительность (другой вопрос) и являются интересными и нетривиальными соображениями. Они могут быть описаны отдельно, но оба должны учитывать решение о выборе одного над другим. Я не могу рекомендовать ни один из ответов на этот или другой вопрос как всеобъемлющий. Есть несколько ответов, которые, взятые вместе, дают довольно полное обсуждение того, как выбрать один над другим.
Стив
1
@Gqqnbig - самый полезный комментарий! Спасибо :-)
Марк Купер

Ответы:

366

Если вам просто не нужен массив для удовлетворения других ограничений, которые вы должны использовать ToList. В большинстве сценариев ToArrayвыделяется больше памяти, чемToList .

Оба используют массивы для хранения, но ToListимеют более гибкое ограничение. Массив должен быть не меньше, чем количество элементов в коллекции. Если массив больше, это не проблема. ОднакоToArray размер массива должен соответствовать количеству элементов.

Чтобы удовлетворить это ограничение, ToArrayчасто делается еще одно распределение, чемToList . Как только у него есть достаточно большой массив, он выделяет массив, который имеет точный размер, и копирует элементы обратно в этот массив. Единственный раз, когда этого можно избежать, это когда алгоритм увеличения массива просто совпадает с количеством элементов, которые необходимо сохранить (определенно в меньшинстве).

РЕДАКТИРОВАТЬ

Несколько человек спросили меня о последствиях наличия дополнительной неиспользуемой памяти в List<T>значении.

Это действительная проблема. Если созданная коллекция является долгоживущей, никогда не модифицируется после создания и имеет высокий шанс попадания в кучу Gen2, тогда вам лучше взять дополнительное выделение ToArrayзаранее.

В целом, хотя я нахожу это более редким случаем. В этом случае гораздо чаще можно увидеть множество ToArrayвызовов, которые сразу же передаются другим недолгим использованиям памяти.ToList это явно лучше.

Ключ здесь, чтобы профиль, профиль, а затем профиль еще.

JaredPar
источник
14
С другой стороны, не будет ли лишняя память, выделенная для работы по созданию массива, подходить для сборки мусора, тогда как дополнительные издержки для List останутся? Я говорю, держи это проще. Если вам нужно добавить или удалить элементы, для этого есть инструмент. Если нет, есть другой инструмент для этого. Используйте тот, который имеет смысл. Если позже вы обнаружите проблему с памятью и производительностью, и это все , измените ее.
Энтони Пеграм
1
@AnthonyPegram да, это правильное соображение. Если значение используется в долговременном хранилище, не будет изменено и потенциально превратится в Gen 2, тогда вам лучше заплатить дополнительное выделение, чем загрязнять кучу Gen 2. IME, хотя я редко вижу это. Гораздо чаще можно увидеть, как ToArray сразу передается другому недолговечному LINQ-запросу.
JaredPar
2
@AnthonyPegram Я обновил свой ответ, чтобы включить эту сторону обсуждения
JaredPar
8
@JaredPar Я не понимаю, как ToArrayможно выделить больше памяти, если ей нужен точный размер местоположений, где, ToList<>очевидно, есть автоматические запасные местоположения. (
автоматическое увеличение
5
@RoyiNamir, поскольку ToArray сначала выполняет распределение в стиле ToList с накладными расходами, а затем выполняет дополнительное распределение точного размера.
Тимбо
169

Разница в производительности будет незначительной, поскольку List<T>реализована в виде динамически изменяемого массива. Вызов ToArray()(который использует внутренний Buffer<T>класс для увеличения массива) или ToList()(который вызываетList<T>(IEnumerable<T>) конструктор) в конечном итоге станет вопросом помещения их в массив и увеличения массива до тех пор, пока он не будет соответствовать всем им.

Если вы хотите получить конкретное подтверждение этого факта, проверьте реализацию соответствующих методов в Reflector - вы увидите, что они сводятся к почти идентичному коду.

MQP
источник
2
Интересный факт, с которым я столкнулся, заключается в том, что для коррелированных запросов, вызванных использованием группы, определенной посредством объединения групп, в вашей проекции, Linq to SQL добавляет еще один подзапрос для получения счетчика для этой группы. Я предполагаю, что это означает, что в этих случаях размер коллекции будет известен до того, как элементы будут извлечены, и, таким образом, может быть создан массив точного размера, который сэкономит ресурсы обработки и памяти при материализации результатов.
jpierson
133
Если счет известен заранее, производительность идентична. Однако, если Count не известен заранее, единственная разница между ToArray()и ToList()заключается в том, что первый должен обрезать избыток, который включает в себя копирование всего массива, тогда как последний не обрезает избыток, а использует в среднем 25 % больше памяти. Это будет иметь последствия, только если тип данных большой struct. Просто пища для размышлений.
Скотт Риппи
9
@EldritchConundrum 25% исходит из этой логики: если число элементов неизвестно, то вызов ToListили ToArrayначнется с создания небольшого буфера. Когда этот буфер заполнен, он удваивает емкость буфера и продолжает работу. Поскольку емкость всегда удваивается, неиспользуемый буфер всегда будет между 0% и 50%.
Скотт Риппи
2
@ScottRippey Я только что искал источник нового List из источника IEnumerable, и он проверяет, является ли IEnumerable ICollection, и если это так, то он начинает с выделения одного массива с точным размером, необходимым из свойства Count, поэтому будет случай, когда ToList () определенно будет быстрее. Полный ответ может включать этот факт, хотя я не думаю, что это самый распространенный случай.
AndyClaw
3
@AndyClaw И то Listи другое Bufferпроверит ICollection, в этом случае производительность будет одинаковой.
Скотт Риппи
54

(семь лет спустя ...)

Несколько других (хороших) ответов были сосредоточены на микроскопических различиях в производительности.

Этот пост является лишь дополнением, чтобы упомянуть семантическое различие, которое существует между IEnumerator<T>созданным array ( T[]) по сравнению с возвращаемымList<T> .

Лучше всего иллюстрируется на примере:

IList<int> source = Enumerable.Range(1, 10).ToArray();  // try changing to .ToList()

foreach (var x in source)
{
  if (x == 5)
    source[8] *= 100;
  Console.WriteLine(x);
}

Приведенный выше код будет работать без исключения и выдает результат:

1
2
3
4
5
6
7
8
900
10

Это показывает, что IEnumarator<int>возвращаемый объект int[]не отслеживает, был ли массив изменен с момента создания перечислителя.

Обратите внимание, что я объявил локальную переменную sourceкак IList<int>. Таким образом, я убедился, что компилятор C # не оптимизирует foreachоператор в нечто, эквивалентное for (var idx = 0; idx < source.Length; idx++) { /* ... */ }циклу. Это то, что компилятор C # может сделать, если я использую var source = ...;вместо этого. В моей текущей версии .NET Framework фактически используемый здесь перечислитель является непубличным ссылочным типом, System.SZArrayHelper+SZGenericArrayEnumerator`1[System.Int32]но, конечно, это деталь реализации.

Теперь, если я меняю .ToArray()в .ToList(), я получаю только:

1
2
3
4
5

сопровождаемый System.InvalidOperationExceptionвзрывной поговоркой:

Коллекция была изменена; Операция перечисления может не выполняться.

Основным перечислителем в этом случае является общедоступный изменяемый тип-значение System.Collections.Generic.List`1+Enumerator[System.Int32]( IEnumerator<int>в данном случае заключенный в рамку, потому что я использую IList<int>).

В заключение перечислитель, созданный с помощьюList<T>отслеживания, меняет ли список во время перечисления, в то время как произведенный перечислителемT[]нет. Поэтому учитывайте эту разницу при выборе между.ToList()и.ToArray().

Люди часто добавляют одну дополнительную .ToArray() или .ToList()обходят коллекцию, которая отслеживает, была ли она изменена в течение жизни счетчика.

(Если кто -то хочет знать , какList<> отслеживает от того, был ли изменен сбор, есть частные поля _versionв этом классе , который изменяется каждый раз List<>обновляется.)

Джепп Стиг Нильсен
источник
28

Я согласен с @mquander, что разница в производительности должна быть незначительной. Тем не менее, я хотел проверить это, чтобы быть уверенным, поэтому я сделал - и это незначительно.

Testing with List<T> source:
ToArray time: 1934 ms (0.01934 ms/call), memory used: 4021 bytes/array
ToList  time: 1902 ms (0.01902 ms/call), memory used: 4045 bytes/List

Testing with array source:
ToArray time: 1957 ms (0.01957 ms/call), memory used: 4021 bytes/array
ToList  time: 2022 ms (0.02022 ms/call), memory used: 4045 bytes/List

Каждый исходный массив / список имел 1000 элементов. Таким образом, вы можете видеть, что разница во времени и в памяти незначительна.

Мой вывод: вы также можете использовать ToList () , поскольку a List<T>предоставляет больше функциональных возможностей, чем массив, если только для вас не имеют значения несколько байт памяти.

EMP
источник
1
Интересно, если бы этот результат был другим, если бы вы использовали большой structвместо примитивного типа или класса.
Скотт Риппи
12
Список <T> .ToList ???? Какой смысл? Лучше попытаться добавить в него IEnumerable, который не реализует интерфейс ICollection.
Григорий
8
Я хотел убедиться, что я измеряю только время вызова ToListили, ToArrayа не перечисление любого IEnumerable. List <T> .ToList () по-прежнему создает новый List <T> - он не просто «возвращает это».
EMP
23
-1 Поведение ToArray()и ToList()слишком сильно отличаются, когда они снабжены ICollection<T>параметром - они просто выполняют одно выделение и одну операцию копирования. И то, List<T>и другое Arrayреализует ICollection<T>, поэтому ваши тесты вообще не действительны.
Мохаммад Дехган
1
Для тех, кто заинтересован, я разместил свой собственный тест в качестве отдельного ответа . Он использует, .Select(i => i)чтобы избежать ICollection<T>проблем с реализацией, и включает контрольную группу, чтобы увидеть, сколько времени уходит на перебор источника IEnumerable<>в первую очередь.
StriplingWarrior
19

ToList()обычно предпочтительнее, если вы используете его IEnumerable<T>(например, из ORM). Если длина последовательности не известна в начале, ToArray()создает коллекцию динамической длины, такую ​​как List, а затем преобразует ее в массив, что занимает дополнительное время.

Виталий Улантиков
источник
26
Я решил, что в этом случае удобочитаемость превосходит производительность. Теперь я использую ToList только тогда, когда ожидаю продолжения добавления элементов. Во всех других случаях (в большинстве случаев) я использую ToArray. Но спасибо за вклад!
Фрэнк Крюгер
5
Смотря в ILSpy, Enumerable.ToArray()звонки new Buffer<TSource>(source).ToArray(). В конструкторе Buffer, если источник реализует ICollection, он вызывает source.CopyTo (items, 0), а затем .ToArray () возвращает массив внутренних элементов напрямую. Таким образом, в этом случае нет преобразования, которое требует дополнительного времени. Если источник не реализует ICollection, то ToArray приведет к копированию массива, чтобы обрезать лишние неиспользуемые местоположения в конце массива, как описано выше в комментарии Скотта Риппи.
BrandonAGr
19

Память всегда будет выделяться дважды - или что-то близкое к этому. Поскольку вы не можете изменить размер массива, оба метода будут использовать какой-то механизм для сбора данных в растущей коллекции. (Ну, список сам по себе является растущей коллекцией.)

Список использует массив в качестве внутреннего хранилища и удваивает емкость при необходимости. Это означает, что в среднем 2/3 предметов было перераспределено по меньшей мере один раз, половина из них перераспределена по меньшей мере дважды, половина - по меньшей мере трижды и т. Д. Это означает, что каждый элемент в среднем был перераспределен в 1,3 раза, что не сильно увеличивает накладные расходы.

Помните также, что если вы собираете строки, сама коллекция содержит только ссылки на строки, сами строки не перераспределяются.

Guffa
источник
Это может быть невежественным вопросом, но разве логика 2/3, 1/3, 1/6, которую вы обрисовали, не предполагает, что массив List может быть расширен на месте? То есть в конце массива есть свободное место, так что существующее распределение не нужно перемещать?
@JonofAllTrades: Нет, массив никогда не расширяется, управление памятью в .NET просто не делает этого. Если он будет расширен на месте, не будет необходимости в перераспределении товаров.
Гуффа
Ах, я вижу: предметы, которые не были перераспределены, не должны были делать это, потому что они были в окончательном распределении. Все элементы, выделенные в предыдущих распределениях, перемещаются, но из-за логарифмического увеличения длины массива это вычисляемая доля. Спасибо за разъяснение!
19

Сейчас 2020, и все используют .NET Core 3.1, поэтому я решил запустить некоторые тесты с Benchmark.NET.

TL; DR: ToArray () лучше с точки зрения производительности и лучше передает намерение, если вы не планируете изменять коллекцию.


    [MemoryDiagnoser]
    public class Benchmarks
    {
        [Params(0, 1, 6, 10, 39, 100, 666, 1000, 1337, 10000)]
        public int Count { get; set; }

        public IEnumerable<int> Items => Enumerable.Range(0, Count);

        [Benchmark(Description = "ToArray()", Baseline = true)]
        public int[] ToArray() => Items.ToArray();

        [Benchmark(Description = "ToList()")]
        public List<int> ToList() => Items.ToList();

        public static void Main() => BenchmarkRunner.Run<Benchmarks>();
    }

Результаты:


    BenchmarkDotNet=v0.12.0, OS=Windows 10.0.14393.3443 (1607/AnniversaryUpdate/Redstone1)
    Intel Core i5-4460 CPU 3.20GHz (Haswell), 1 CPU, 4 logical and 4 physical cores
    Frequency=3124994 Hz, Resolution=320.0006 ns, Timer=TSC
    .NET Core SDK=3.1.100
      [Host]     : .NET Core 3.1.0 (CoreCLR 4.700.19.56402, CoreFX 4.700.19.56404), X64 RyuJIT
      DefaultJob : .NET Core 3.1.0 (CoreCLR 4.700.19.56402, CoreFX 4.700.19.56404), X64 RyuJIT


    |    Method | Count |          Mean |       Error |      StdDev |        Median | Ratio | RatioSD |   Gen 0 | Gen 1 | Gen 2 | Allocated |
    |---------- |------ |--------------:|------------:|------------:|--------------:|------:|--------:|--------:|------:|------:|----------:|
    | ToArray() |     0 |      7.357 ns |   0.2096 ns |   0.1960 ns |      7.323 ns |  1.00 |    0.00 |       - |     - |     - |         - |
    |  ToList() |     0 |     13.174 ns |   0.2094 ns |   0.1958 ns |     13.084 ns |  1.79 |    0.05 |  0.0102 |     - |     - |      32 B |
    |           |       |               |             |             |               |       |         |         |       |       |           |
    | ToArray() |     1 |     23.917 ns |   0.4999 ns |   0.4676 ns |     23.954 ns |  1.00 |    0.00 |  0.0229 |     - |     - |      72 B |
    |  ToList() |     1 |     33.867 ns |   0.7350 ns |   0.6876 ns |     34.013 ns |  1.42 |    0.04 |  0.0331 |     - |     - |     104 B |
    |           |       |               |             |             |               |       |         |         |       |       |           |
    | ToArray() |     6 |     28.242 ns |   0.5071 ns |   0.4234 ns |     28.196 ns |  1.00 |    0.00 |  0.0280 |     - |     - |      88 B |
    |  ToList() |     6 |     43.516 ns |   0.9448 ns |   1.1949 ns |     42.896 ns |  1.56 |    0.06 |  0.0382 |     - |     - |     120 B |
    |           |       |               |             |             |               |       |         |         |       |       |           |
    | ToArray() |    10 |     31.636 ns |   0.5408 ns |   0.4516 ns |     31.657 ns |  1.00 |    0.00 |  0.0331 |     - |     - |     104 B |
    |  ToList() |    10 |     53.870 ns |   1.2988 ns |   2.2403 ns |     53.415 ns |  1.77 |    0.07 |  0.0433 |     - |     - |     136 B |
    |           |       |               |             |             |               |       |         |         |       |       |           |
    | ToArray() |    39 |     58.896 ns |   0.9441 ns |   0.8369 ns |     58.548 ns |  1.00 |    0.00 |  0.0713 |     - |     - |     224 B |
    |  ToList() |    39 |    138.054 ns |   2.8185 ns |   3.2458 ns |    138.937 ns |  2.35 |    0.08 |  0.0815 |     - |     - |     256 B |
    |           |       |               |             |             |               |       |         |         |       |       |           |
    | ToArray() |   100 |    119.167 ns |   1.6195 ns |   1.4357 ns |    119.120 ns |  1.00 |    0.00 |  0.1478 |     - |     - |     464 B |
    |  ToList() |   100 |    274.053 ns |   5.1073 ns |   4.7774 ns |    272.242 ns |  2.30 |    0.06 |  0.1578 |     - |     - |     496 B |
    |           |       |               |             |             |               |       |         |         |       |       |           |
    | ToArray() |   666 |    569.920 ns |  11.4496 ns |  11.2450 ns |    571.647 ns |  1.00 |    0.00 |  0.8688 |     - |     - |    2728 B |
    |  ToList() |   666 |  1,621.752 ns |  17.1176 ns |  16.0118 ns |  1,623.566 ns |  2.85 |    0.05 |  0.8793 |     - |     - |    2760 B |
    |           |       |               |             |             |               |       |         |         |       |       |           |
    | ToArray() |  1000 |    796.705 ns |  16.7091 ns |  19.8910 ns |    796.610 ns |  1.00 |    0.00 |  1.2951 |     - |     - |    4064 B |
    |  ToList() |  1000 |  2,453.110 ns |  48.1121 ns |  65.8563 ns |  2,460.190 ns |  3.09 |    0.10 |  1.3046 |     - |     - |    4096 B |
    |           |       |               |             |             |               |       |         |         |       |       |           |
    | ToArray() |  1337 |  1,057.983 ns |  20.9810 ns |  41.4145 ns |  1,041.028 ns |  1.00 |    0.00 |  1.7223 |     - |     - |    5416 B |
    |  ToList() |  1337 |  3,217.550 ns |  62.3777 ns |  61.2633 ns |  3,203.928 ns |  2.98 |    0.13 |  1.7357 |     - |     - |    5448 B |
    |           |       |               |             |             |               |       |         |         |       |       |           |
    | ToArray() | 10000 |  7,309.844 ns | 160.0343 ns | 141.8662 ns |  7,279.387 ns |  1.00 |    0.00 | 12.6572 |     - |     - |   40064 B |
    |  ToList() | 10000 | 23,858.032 ns | 389.6592 ns | 364.4874 ns | 23,759.001 ns |  3.26 |    0.08 | 12.6343 |     - |     - |   40096 B |

    // * Hints *
    Outliers
      Benchmarks.ToArray(): Default -> 2 outliers were removed (35.20 ns, 35.29 ns)
      Benchmarks.ToArray(): Default -> 2 outliers were removed (38.51 ns, 38.88 ns)
      Benchmarks.ToList(): Default  -> 1 outlier  was  removed (64.69 ns)
      Benchmarks.ToArray(): Default -> 1 outlier  was  removed (67.02 ns)
      Benchmarks.ToArray(): Default -> 1 outlier  was  removed (130.08 ns)
      Benchmarks.ToArray(): Default -> 1 outlier  was  detected (541.82 ns)
      Benchmarks.ToArray(): Default -> 1 outlier  was  removed (7.82 us)

    // * Legends *
      Count     : Value of the 'Count' parameter
      Mean      : Arithmetic mean of all measurements
      Error     : Half of 99.9% confidence interval
      StdDev    : Standard deviation of all measurements
      Median    : Value separating the higher half of all measurements (50th percentile)
      Ratio     : Mean of the ratio distribution ([Current]/[Baseline])
      RatioSD   : Standard deviation of the ratio distribution ([Current]/[Baseline])
      Gen 0     : GC Generation 0 collects per 1000 operations
      Gen 1     : GC Generation 1 collects per 1000 operations
      Gen 2     : GC Generation 2 collects per 1000 operations
      Allocated : Allocated memory per single operation (managed only, inclusive, 1KB = 1024B)
      1 ns      : 1 Nanosecond (0.000000001 sec)
Tyrrrz
источник
1
Если вы не планируете изменять коллекцию, я думаю, что цель лучше показать с помощью ToImmutableArray()(из пакета System.Collections.Immutable) Art
Артуро Торрес Санчес
@ ArturoTorresSánchez true, но если коллекция не предоставляется вне метода, я бы просто использовал массив.
Тырррз
2
Спасибо за это. Выбранный ответ является простым аргументом и предполагает результаты, следующие за этим аргументом. Чтобы сделать это с научной точки зрения и в качестве бонуса узнать, какая разница, существует только один реальный способ узнать.
Джонас
15

Редактировать : последняя часть этого ответа недействительна. Впрочем, остальное пока полезная информация, поэтому я ее оставлю.

Я знаю, что это старый пост, но после того же вопроса и исследования я нашел кое-что интересное, чем стоит поделиться.

Во-первых, я согласен с @mquander и его ответом. Он прав, говоря, что с точки зрения производительности, они идентичны.

Тем не менее, я использовал Reflector, чтобы взглянуть на методы в System.Linq.Enumerableпространстве имен расширений, и заметил очень распространенную оптимизацию.
Когда это возможно, IEnumerable<T>источник приводится IList<T>или ICollection<T>оптимизирует метод. Например, посмотрите на ElementAt(int).

Интересно, что Microsoft решила оптимизировать только для IList<T>, но не для IList. Похоже, Microsoft предпочитает использовать IList<T>интерфейс.

System.Arrayтолько реализует IList, поэтому он не выиграет ни от одной из этих оптимизаций расширения.
Поэтому я утверждаю, что наилучшей практикой является использование .ToList()метода.
Если вы используете какой-либо из методов расширения или передаете список другому методу, есть вероятность, что он может быть оптимизирован для IList<T>.

Скотт Риппи
источник
16
Я сделал тест и обнаружил что-то удивительное. Массив реализует IList <T>! Использование Reflector для анализа System.Array показывает только цепочку наследования IList, ICollection, IEnumerable, но используя отражение во время выполнения, я обнаружил, что string [] имеет цепочку наследования IList, ICollection, IEnumerable, IList <string>, ICollection <string >, IEnumerable <string>. Поэтому у меня нет лучшего ответа, чем @mquander!
Скотт Риппи
@ScottRippey Да. Странное наблюдение, которое вы заметили, на самом деле является частью «хака» - и оно также имеет довольно странные последствия в отношении «фиксированного размера» и аналогичных свойств (с некоторыми несоответствиями в зависимости от того, как вы их разыгрываете). Есть несколько довольно больших комментариев, касающихся этой темы, внутри исходного кода .net. Извините за отсутствие ссылок, но если я правильно помню, это довольно легко найти (внутри класса-массива). (И есть еще один большой вопрос, обсуждающий несоответствия .... где-то ...> __>)
AnorZaken
@ScottRippey просто к вашему сведению Я нашел этот ответ, связанный с вашим комментарием: stackoverflow.com/a/4482567/2063755
Дэвид Клемпфнер,
14

Я обнаружил, что других эталонных тестов, которые люди здесь сделали, не хватает, так что вот мой недостаток. Дайте мне знать, если вы нашли что-то не так с моей методологией.

/* This is a benchmarking template I use in LINQPad when I want to do a
 * quick performance test. Just give it a couple of actions to test and
 * it will give you a pretty good idea of how long they take compared
 * to one another. It's not perfect: You can expect a 3% error margin
 * under ideal circumstances. But if you're not going to improve
 * performance by more than 3%, you probably don't care anyway.*/
void Main()
{
    // Enter setup code here
    var values = Enumerable.Range(1, 100000)
        .Select(i => i.ToString())
        .ToArray()
        .Select(i => i);
    values.GetType().Dump();
    var actions = new[]
    {
        new TimedAction("ToList", () =>
        {
            values.ToList();
        }),
        new TimedAction("ToArray", () =>
        {
            values.ToArray();
        }),
        new TimedAction("Control", () =>
        {
            foreach (var element in values)
            {
                // do nothing
            }
        }),
        // Add tests as desired
    };
    const int TimesToRun = 1000; // Tweak this as necessary
    TimeActions(TimesToRun, actions);
}


#region timer helper methods
// Define other methods and classes here
public void TimeActions(int iterations, params TimedAction[] actions)
{
    Stopwatch s = new Stopwatch();
    int length = actions.Length;
    var results = new ActionResult[actions.Length];
    // Perform the actions in their initial order.
    for (int i = 0; i < length; i++)
    {
        var action = actions[i];
        var result = results[i] = new ActionResult { Message = action.Message };
        // Do a dry run to get things ramped up/cached
        result.DryRun1 = s.Time(action.Action, 10);
        result.FullRun1 = s.Time(action.Action, iterations);
    }
    // Perform the actions in reverse order.
    for (int i = length - 1; i >= 0; i--)
    {
        var action = actions[i];
        var result = results[i];
        // Do a dry run to get things ramped up/cached
        result.DryRun2 = s.Time(action.Action, 10);
        result.FullRun2 = s.Time(action.Action, iterations);
    }
    results.Dump();
}

public class ActionResult
{
    public string Message { get; set; }
    public double DryRun1 { get; set; }
    public double DryRun2 { get; set; }
    public double FullRun1 { get; set; }
    public double FullRun2 { get; set; }
}

public class TimedAction
{
    public TimedAction(string message, Action action)
    {
        Message = message;
        Action = action;
    }
    public string Message { get; private set; }
    public Action Action { get; private set; }
}

public static class StopwatchExtensions
{
    public static double Time(this Stopwatch sw, Action action, int iterations)
    {
        sw.Restart();
        for (int i = 0; i < iterations; i++)
        {
            action();
        }
        sw.Stop();

        return sw.Elapsed.TotalMilliseconds;
    }
}
#endregion

Вы можете скачать скрипт LINQPad здесь .

Результаты: ToArray против производительности ToList

Изменяя код выше, вы обнаружите, что:

  1. Разница менее значительна при работе с меньшими массивами . Больше итераций, но меньше массивов
  2. Разница менее значима, когда речь идет о ints, а не strings.
  3. Использование больших structs вместо strings обычно занимает намного больше времени, но на самом деле не сильно меняет соотношение.

Это согласуется с выводами ответов с наибольшим количеством голосов:

  1. Вы вряд ли заметите разницу в производительности, если ваш код часто создает много больших списков данных. (Разница составляла всего 200 мс при создании 1000 списков по 100 тыс. Строк).
  2. ToList() постоянно работает быстрее и будет лучшим выбором, если вы не планируете долго держаться за результаты.

Обновить

@JonHanna указала, что в зависимости от реализации Selectдля реализации ToList()или может быть возможно ToArray()заранее предсказать размер результирующей коллекции. Замена .Select(i => i)в приведенном выше коде Where(i => true) дает очень похожие результаты на данный момент, и, скорее всего, это будет сделано независимо от реализации .NET.

Бенчмарк с использованием Where вместо Select

StriplingWarrior
источник
В .NET Core оба случая должны быть лучше, чем в netfx, потому что он поймет, что размер будет, 100000и использует его для оптимизации обоих, ToList()и ToArray(), ToArray()будучи немного более легким, потому что ему не нужна операция сжатия, в которой он нуждался бы в противном случае, это единственное место, которое ToList()имеет преимущество. Пример в вопросе все равно проиграл бы, потому что Whereсредства такого предсказания размера не могут быть сделаны.
Джон Ханна,
@JonHanna: Спасибо за быстрый отзыв. Я не знал, что .NET Core делает эту оптимизацию. Это классно. В моем коде, .Select(i => i)может быть заменено, .Where(i => true)чтобы исправить это.
StriplingWarrior
Да, это остановит оптимизацию, затрагивающую это на corefx. Может быть интересно иметь как размер, равный степени двух (что должно давать ToArray()преимущество), так и размер, который не соответствует описанному выше, и сравнивать результаты.
Джон Ханна
@JonHanna: Интересно, что по- ToArray() прежнему проигрывает в лучшем случае. С Math.Pow(2, 15)элементами это (ToList: 700мс, ToArray: 900мс). Добавление еще одного элемента увеличивает его (ToList: 925, ToArray: 1350). Интересно, ToArrayвсе еще копирует массив, даже когда он уже имеет идеальный размер? Они, вероятно, полагали, что это было достаточно редкое явление, которое не стоило дополнительных условий.
StriplingWarrior
Он не копировал точное совпадение по размеру, даже до того, как мы начали оптимизировать его в corefx, так что это тот случай, когда он получает наибольшее количество разрывов.
Джон Ханна
12

Вы должны основывать свое решение на том ToListили ToArrayином выборе дизайна. Если вы хотите коллекцию, которая может быть повторена и доступна только по индексу, выберите ToArray. Если вам нужны дополнительные возможности добавления и удаления из коллекции позже без особых хлопот, сделайте это ToList(на самом деле вы не можете добавить массив, но обычно это не самый подходящий инструмент для него).

Если производительность имеет значение, вы должны также подумать, что будет быстрее работать. Реально, вы не будете звонить ToListили ToArrayмиллион раз, но можете работать с полученным сбором миллион раз. В этом отношении []лучше, так List<>как []с некоторыми накладными расходами. Посмотрите эту ветку для сравнения эффективности: какая из них более эффективна: List <int> или int []

В моих собственных тестах некоторое время назад я нашел ToArrayбыстрее. И я не уверен, насколько искажены были тесты. Разница в производительности настолько незначительна, что заметна, только если вы выполняете эти запросы в цикле миллионы раз.

Навфал
источник
2
Да - если компилятор знает, что вы выполняете итерации по массиву (а не IEnumerable <>), он может значительно оптимизировать итерацию.
RobSiklos
12

Очень поздний ответ, но я думаю, что он будет полезен для Google.

Они оба отстой, когда они созданы с помощью linq. Они оба реализуют один и тот же код для изменения размера буфера, если это необходимо . ToArrayвнутренне использует класс для преобразования IEnumerable<>в массив, выделяя массив из 4 элементов. Если этого недостаточно, он удваивает размер, создавая новый массив, удваивая размер текущего и копируя в него текущий массив. В конце он выделяет новый массив количества ваших предметов. Если ваш запрос возвращает 129 элементов, то ToArray сделает 6 выделений и операций копирования памяти, чтобы создать массив из 256 элементов, а затем - еще один массив из 129, который нужно вернуть. так много для эффективности памяти.

ToList делает то же самое, но пропускает последнее распределение, так как вы можете добавлять элементы в будущем. Список не заботится, создан ли он из запроса linq или создан вручную.

для создания List лучше с памятью, но хуже с процессором, так как list - универсальное решение, каждое действие требует проверки диапазона в дополнение к внутренней проверке диапазона .net для массивов.

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

Распределение инициализации списка может быть лучше, если вы укажете параметр емкости при его создании. В этом случае он будет выделять массив только один раз, при условии, что вы знаете размер результата. ToListВ linq не указана перегрузка для ее предоставления, поэтому мы должны создать наш метод расширения, который создает список с заданной емкостью, а затем использует List<>.AddRange.

Чтобы закончить этот ответ, я должен написать следующие предложения

  1. В конце вы можете использовать либо ToArray, либо ToList, производительность не будет такой разной (см. Ответ @EMP).
  2. Вы используете C #. Если вам нужна производительность, не беспокойтесь о написании кода с высокой производительностью, а не о том, чтобы не писать код с плохой производительностью.
  3. Всегда нацеливайтесь на x64 для высокопроизводительного кода. AFAIK, x64 JIT основан на компиляторе C ++ и выполняет некоторые забавные вещи, такие как оптимизация хвостовой рекурсии.
  4. С 4.5 вы также можете наслаждаться оптимизацией по профилю и многоядерным JIT.
  5. Наконец, вы можете использовать шаблон async / await для более быстрой обработки.
Эрдоган Куртур
источник
Они оба сосут? У вас есть альтернативная идея, которая не требует избыточного выделения памяти?
Nawfal
В контексте вопроса, да, они оба отстой, но из-за избыточного распределения, и ничего больше. Чтобы уменьшить избыточное распределение, можно использовать связанные списки за счет памяти и скорости итерации. В конце концов, это то, что мы делаем, мы делаем компромиссы. Другая идея, если создать список с емкостью 200 (например), а затем загрузить элементы. Это также уменьшит избыточность, но массивы всегда быстрее, так что это еще один компромисс.
Эрдоган Куртур
Создать список из 200 ? Это могло бы избежать изменения размера, но я говорил об избыточной используемой памяти. Вы не можете помочь, потому что нет никаких предварительных знаний о том, какой размер может быть. Вы уже можете указать емкость в конструкторе a List<T>, но когда вы этого не сделаете или не сможете, вы не сможете ничего с этим поделать.
nawfal
2
единственные избыточные данные в памяти - это содержимое массива, представляющего собой список указателей (в данном случае). один миллион 64-битных указателей занимает до 8 МБ памяти, что ничто по сравнению с одним миллионом объектов, на которые они указывают. 200 - это просто число, и оно может уменьшить количество вызовов с изменением размера максимум в 5 раз. и да, мы не можем с этим поделать. у нас нет лучших вариантов. У меня нет лучшего решения, но это не значит, что я не могу сказать, где проблема.
Эрдоган Куртур
1
хм, в конце концов, это где вы рисуете линию. Мне нравится текущая реализация. Тон вашего ответа заставил меня думать, что это была критика, а не где проблема :)
nawfal
7

Это старый вопрос - но для удобства пользователей, которые сталкиваются с ним, существует также и альтернатива «Memoizing» для Enumerable - который приводит к кешированию и прекращению многократного перечисления оператора Linq, чем и является ToArray () и ToList () используются много, хотя атрибуты коллекции списка или массива никогда не используются.

Memoize доступен в библиотеке RX / System.Interactive и объясняется здесь: Больше LINQ с System.Interactive

(Из блога Барта де Смета, который настоятельно рекомендуется прочитать, если вы много работаете с Linq to Objects)

Frep D-Oronge
источник
4

Один из вариантов - добавить собственный метод расширения, который возвращает только для чтения ICollection<T> . Это может быть лучше, чем использование, ToListили ToArrayкогда вы не хотите использовать либо свойства индексации массива / списка, либо добавлять / удалять из списка.

public static class EnumerableExtension
{
    /// <summary>
    /// Causes immediate evaluation of the linq but only if required.
    /// As it returns a readonly ICollection, is better than using ToList or ToArray
    /// when you do not want to use the indexing properties of an IList, or add to the collection.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="enumerable"></param>
    /// <returns>Readonly collection</returns>
    public static ICollection<T> Evaluate<T>(this IEnumerable<T> enumerable)
    {
        //if it's already a readonly collection, use it
        var collection = enumerable as ICollection<T>;
        if ((collection != null) && collection.IsReadOnly)
        {
            return collection;
        }
        //or make a new collection
        return enumerable.ToList().AsReadOnly();
    }
}

Модульные тесты:

[TestClass]
public sealed class EvaluateLinqTests
{
    [TestMethod]
    public void EvalTest()
    {
        var list = new List<int> {1, 2, 3};
        var linqResult = list.Select(i => i);
        var linqResultEvaluated = list.Select(i => i).Evaluate();
        list.Clear();
        Assert.AreEqual(0, linqResult.Count());
        //even though we have cleared the underlying list, the evaluated list does not change
        Assert.AreEqual(3, linqResultEvaluated.Count());
    }

    [TestMethod]
    public void DoesNotSaveCreatingListWhenHasListTest()
    {
        var list = new List<int> {1, 2, 3};
        var linqResultEvaluated = list.Evaluate();
        //list is not readonly, so we expect a new list
        Assert.AreNotSame(list, linqResultEvaluated);
    }

    [TestMethod]
    public void SavesCreatingListWhenHasReadonlyListTest()
    {
        var list = new List<int> {1, 2, 3}.AsReadOnly();
        var linqResultEvaluated = list.Evaluate();
        //list is readonly, so we don't expect a new list
        Assert.AreSame(list, linqResultEvaluated);
    }

    [TestMethod]
    public void SavesCreatingListWhenHasArrayTest()
    {
        var list = new[] {1, 2, 3};
        var linqResultEvaluated = list.Evaluate();
        //arrays are readonly (wrt ICollection<T> interface), so we don't expect a new object
        Assert.AreSame(list, linqResultEvaluated);
    }

    [TestMethod]
    [ExpectedException(typeof (NotSupportedException))]
    public void CantAddToResultTest()
    {
        var list = new List<int> {1, 2, 3};
        var linqResultEvaluated = list.Evaluate();
        Assert.AreNotSame(list, linqResultEvaluated);
        linqResultEvaluated.Add(4);
    }

    [TestMethod]
    [ExpectedException(typeof (NotSupportedException))]
    public void CantRemoveFromResultTest()
    {
        var list = new List<int> {1, 2, 3};
        var linqResultEvaluated = list.Evaluate();
        Assert.AreNotSame(list, linqResultEvaluated);
        linqResultEvaluated.Remove(1);
    }
}
Уэстон
источник
Стоит отметить, что договор о коллекции только для чтения предусматривает, что пользователь объекта не может изменять его, но владелец может сделать это, если он сохраняет ссылку на него, который предлагает изменяемый интерфейс. Для интерфейсов, которые гарантируют, что базовая структура никогда не изменится, посмотрите на неизменные коллекции. Что касается того, почему неизменяемые или доступные только для чтения или простые коллекции для чтения и записи лучше или хуже, для сравнения нужен эталон; нет окончательного ответа (иначе нам не пришлось бы выбирать).
TNE
@tne Обратите внимание, я делаю Tolist до AsReadOnly, поэтому нет никаких ссылок на основной изменяемый.
Уэстон
Вы совершенно правы, и это был, вероятно, лучший способ сделать что-то, прежде чем неизменные коллекции поступили в BCL (первая бета вышла через месяц после вашего ответа).
TNE
Неизменяемые коллекции существуют для безопасности потоков, где потоки могут предполагать, что это не изменится, и если это произойдет, создается новая версия, вместо того, чтобы конкурировать с читателями и изменять ее, пока они ее используют. Таким образом, никому не нужно приобретать замок.
doug65536
4

ToListAsync<T>() является предпочтительным.

В Entity Framework 6 оба метода в конечном итоге вызывают один и тот же внутренний метод, но ToArrayAsync<T>()вызывают list.ToArray()в конце, который реализован как

T[] array = new T[_size];
Array.Copy(_items, 0, array, 0, _size);
return array;

Так что ToArrayAsync<T>()имеет некоторые накладные расходы, поэтому ToListAsync<T>()является предпочтительным.

Стивен Зенг
источник
1
Это на самом деле ответ, который я искал, как EF это делает. Мне было бы любопытно, как это в EF Core.
Шимми Вайцхандлер
3

Старый вопрос, но новые вопросы всегда.

Согласно источнику System.Linq.Enumerable , ToListпросто верните a new List(source), а ToArrayиспользуйте a new Buffer<T>(source).ToArray()для возврата a T[].

О распределении памяти:

При работе на IEnumerable<T>единственном объекте ToArrayвыделяйте память еще один раз, чем ToList. Но вам не нужно заботиться об этом в большинстве случаев, потому что GC будет выполнять сборку мусора при необходимости.

О времени выполнения эффективного:

Те, кто задают этот вопрос, могут запустить следующий код на своей машине, и вы получите ответ.

class PersonC
{
    public Guid uuid;
    public string name;
    public int age;
    public bool sex;
    public DateTime BirthDay;
    public double weight;
}

struct PersonS
{
    public Guid uuid;
    public string name;
    public int age;
    public bool sex;
    public DateTime BirthDay;
    public double weight;
}

class PersonT<T> : IEnumerable<T>
{
    private List<T> items;
    public PersonT(IEnumerable<T> init)
    {
        items = new List<T>(init);
    }

    public IEnumerator<T> GetEnumerator() => items.GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => items.GetEnumerator();
}

private IEnumerable<PersonC> C(int count)
{
    for (var i = 0; i < count; ++i)
    {
        var guid = Guid.NewGuid();
        var guidBytes = guid.ToByteArray(); //16 bytes
        yield return new PersonC
        {
            uuid = guid,
            name = guid.ToString(),
            age = guidBytes[0] ^ guidBytes[7],
            sex = guidBytes[14] % 2 == 0,
            BirthDay = DateTime.Now.AddDays(-guidBytes[11] * 18),
            weight = guidBytes[12] * 100
        };
    }
}

private IEnumerable<PersonS> S(int count)
{
    for (var i = 0; i < count; ++i)
    {
        var guid = Guid.NewGuid();
        var guidBytes = guid.ToByteArray(); //16 bytes
        yield return new PersonS
        {
            uuid = guid,
            name = guid.ToString(),
            age = guidBytes[0] ^ guidBytes[7],
            sex = guidBytes[14] % 2 == 0,
            BirthDay = DateTime.Now.AddDays(-guidBytes[11] * 18),
            weight = guidBytes[12] * 100
        };
    }
}

private void MakeLog(string test, List<long> log) =>
    Console.WriteLine("{0} {1} ms -> [{2}]",
        test,
        log.Average(),
        string.Join(", ", log)
    );

private void Test1(int times, int count)
{
    var test = Enumerable.Range(1, times).ToArray();

    MakeLog("C.ToList", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = C(count).ToList();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("C.ToArray", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = C(count).ToArray();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("S.ToList", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = S(count).ToList();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("S.ToArray", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = S(count).ToArray();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());
}

private void Test2(int times, int count)
{
    var test = Enumerable.Range(1, times).ToArray();

    var dataC1 = new PersonT<PersonC>(C(count));
    var dataS1 = new PersonT<PersonS>(S(count));

    MakeLog("C1.ToList", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataC1.ToList();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("C1.ToArray", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataC1.ToArray();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("S1.ToList", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataS1.ToList();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("S1.ToArray", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataS1.ToArray();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());
}

private void Test3(int times, int count)
{
    var test = Enumerable.Range(1, times).ToArray();

    var dataC2 = (ICollection<PersonC>) new List<PersonC>(C(count));
    var dataS2 = (ICollection<PersonS>) new List<PersonS>(S(count));

    MakeLog("C2.ToList", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataC2.ToList();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("C2.ToArray", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataC2.ToArray();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("S2.ToList", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataS2.ToList();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("S2.ToArray", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataS2.ToArray();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());
}

private void TestMain()
{
    const int times = 100;
    const int count = 1_000_000 + 1;
    Test1(times, count);
    Test2(times, count);
    Test3(times, count);
}

Я получил эти результаты на моей машине:

Группа 1:

C.ToList 761.79 ms -> [775, 755, 759, 759, 756, 759, 765, 750, 757, 762, 759, 754, 757, 753, 763, 753, 759, 756, 768, 754, 763, 757, 757, 777, 780, 758, 754, 758, 762, 754, 758, 757, 763, 758, 760, 754, 761, 755, 764, 847, 952, 755, 747, 763, 760, 758, 754, 763, 761, 758, 750, 764, 757, 763, 762, 756, 753, 759, 759, 757, 758, 779, 765, 760, 760, 756, 760, 756, 755, 764, 759, 753, 757, 760, 752, 764, 758, 760, 758, 760, 755, 761, 751, 753, 761, 762, 761, 758, 759, 752, 765, 756, 760, 755, 757, 753, 760, 751, 755, 779]
C.ToArray 782.56 ms -> [783, 774, 771, 771, 773, 774, 775, 775, 772, 770, 771, 774, 771, 1023, 975, 772, 767, 776, 771, 779, 772, 779, 775, 771, 775, 773, 775, 771, 765, 774, 770, 781, 772, 771, 781, 762, 817, 770, 775, 779, 769, 774, 763, 775, 777, 769, 777, 772, 775, 778, 775, 771, 770, 774, 772, 769, 772, 769, 774, 775, 768, 775, 769, 774, 771, 776, 774, 773, 778, 769, 778, 767, 770, 787, 783, 779, 771, 768, 805, 780, 779, 767, 773, 771, 773, 785, 1044, 853, 775, 774, 775, 771, 770, 769, 770, 776, 770, 780, 821, 770]
S.ToList 704.2 ms -> [687, 702, 709, 691, 694, 710, 696, 698, 700, 694, 701, 719, 706, 694, 702, 699, 699, 703, 704, 701, 703, 705, 697, 707, 691, 697, 707, 692, 721, 698, 695, 700, 704, 700, 701, 710, 700, 705, 697, 711, 694, 700, 695, 698, 701, 692, 696, 702, 690, 699, 708, 700, 703, 714, 701, 697, 700, 699, 694, 701, 697, 696, 699, 694, 709, 1068, 690, 706, 699, 699, 695, 708, 695, 704, 704, 700, 695, 704, 695, 696, 702, 700, 710, 708, 693, 697, 702, 694, 700, 706, 699, 695, 706, 714, 704, 700, 695, 697, 707, 704]
S.ToArray 742.5 ms -> [742, 743, 733, 745, 741, 724, 738, 745, 728, 732, 740, 727, 739, 740, 726, 744, 758, 732, 744, 745, 730, 739, 738, 723, 745, 757, 729, 741, 736, 724, 744, 756, 739, 766, 737, 725, 741, 742, 736, 748, 742, 721, 746, 1043, 806, 747, 731, 727, 742, 742, 726, 738, 746, 727, 739, 743, 730, 744, 753, 741, 739, 746, 728, 740, 744, 734, 734, 738, 731, 747, 736, 731, 765, 735, 726, 740, 743, 730, 746, 742, 725, 731, 757, 734, 738, 741, 732, 747, 744, 721, 742, 741, 727, 745, 740, 730, 747, 760, 737, 740]

C1.ToList 32.34 ms -> [35, 31, 31, 31, 32, 31, 31, 31, 31, 31, 31, 31, 31, 31, 33, 32, 31, 31, 31, 31, 30, 32, 31, 31, 31, 31, 32, 30, 31, 31, 31, 30, 32, 31, 31, 31, 36, 31, 31, 31, 32, 30, 31, 32, 31, 31, 31, 31, 31, 32, 31, 31, 31, 31, 33, 32, 31, 32, 31, 31, 33, 31, 31, 31, 31, 31, 32, 31, 32, 31, 34, 38, 68, 42, 79, 33, 31, 31, 31, 31, 31, 30, 30, 30, 30, 31, 31, 31, 31, 32, 31, 32, 31, 31, 31, 32, 33, 33, 31, 31]
C1.ToArray 56.32 ms -> [57, 56, 59, 54, 54, 55, 56, 57, 54, 54, 55, 55, 57, 56, 59, 57, 56, 58, 56, 56, 54, 56, 57, 55, 55, 55, 57, 58, 57, 58, 55, 55, 56, 55, 57, 56, 56, 59, 56, 56, 56, 56, 58, 56, 57, 56, 56, 57, 56, 55, 56, 56, 56, 59, 56, 56, 56, 55, 55, 54, 55, 54, 57, 56, 56, 56, 55, 55, 56, 56, 56, 59, 56, 56, 57, 56, 57, 56, 56, 56, 56, 62, 55, 56, 56, 56, 69, 57, 58, 56, 57, 58, 56, 57, 56, 56, 56, 56, 56, 56]
S1.ToList 88.69 ms -> [96, 90, 90, 89, 91, 88, 89, 90, 96, 89, 89, 89, 90, 90, 90, 89, 90, 90, 89, 90, 89, 91, 89, 91, 89, 91, 89, 90, 90, 89, 87, 88, 87, 88, 87, 87, 87, 87, 88, 88, 87, 87, 89, 87, 87, 87, 91, 88, 87, 86, 89, 87, 90, 89, 89, 90, 89, 87, 87, 87, 86, 87, 88, 90, 88, 87, 87, 92, 87, 87, 88, 88, 88, 86, 86, 87, 88, 87, 87, 87, 89, 87, 89, 87, 90, 89, 89, 89, 91, 89, 90, 89, 90, 88, 90, 90, 90, 88, 89, 89]
S1.ToArray 143.26 ms -> [130, 129, 130, 131, 133, 130, 131, 130, 135, 137, 130, 136, 132, 131, 130, 131, 132, 130, 132, 136, 130, 131, 157, 153, 194, 364, 176, 189, 203, 194, 189, 192, 183, 140, 142, 147, 145, 134, 159, 158, 142, 167, 130, 143, 145, 144, 160, 154, 156, 153, 153, 164, 142, 145, 137, 134, 145, 143, 142, 135, 133, 133, 135, 134, 134, 139, 139, 133, 134, 141, 133, 132, 133, 132, 133, 131, 135, 132, 133, 132, 128, 128, 130, 132, 129, 129, 129, 129, 129, 128, 134, 129, 129, 129, 129, 128, 128, 137, 130, 131]

C2.ToList 3.25 ms -> [5, 3, 3, 3, 3, 4, 3, 4, 3, 4, 3, 3, 3, 4, 4, 3, 3, 3, 4, 3, 3, 3, 3, 4, 3, 4, 3, 4, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 3, 4, 3, 3, 3, 3, 3, 4, 3, 4, 3, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 4, 3, 4, 3, 3, 3, 4, 3, 3, 4, 3, 3, 3, 4, 3, 3, 3, 3, 3]
C2.ToArray 3.37 ms -> [4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 4, 3, 5, 4, 9, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 4, 4, 3, 3, 4, 4, 3, 3, 3, 4, 3, 3, 3, 3, 4, 3, 3, 3, 3, 3, 4, 4, 3, 4, 4, 3, 3, 4, 3, 3, 4, 3, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 4, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 4, 3, 4, 3, 3, 3]
S2.ToList 37.72 ms -> [38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 40, 38, 38, 39, 39, 38, 38, 38, 38, 37, 37, 37, 37, 39, 37, 37, 39, 38, 37, 37, 37, 37, 39, 38, 37, 37, 38, 37, 38, 37, 37, 38, 37, 37, 37, 38, 37, 37, 36, 37, 38, 37, 39, 37, 39, 38, 37, 38, 38, 38, 38, 38, 38, 37, 38, 38, 38, 38, 38, 37, 38, 37, 37, 38, 37, 37, 39, 41, 37, 38, 38, 37, 37, 37, 37, 38, 37, 37, 37, 40, 37, 37, 37, 37, 39, 38]
S2.ToArray 38.86 ms -> [39, 37, 39, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 39, 38, 38, 38, 38, 38, 38, 38, 38, 39, 39, 38, 38, 38, 39, 37, 38, 38, 38, 38, 38, 37, 37, 38, 37, 37, 38, 38, 40, 38, 38, 38, 38, 38, 39, 38, 38, 39, 38, 38, 39, 38, 38, 40, 38, 39, 38, 38, 39, 38, 38, 38, 38, 38, 39, 38, 38, 38, 39, 39, 37, 38, 38, 39, 71, 78, 37, 37, 37, 39, 38, 38, 39, 38, 38, 38, 38, 38, 39, 38, 38, 38, 39, 38, 38, 38]

Group2:

C.ToList 756.81 ms
C.ToArray 774.21 ms
S.ToList 709.7 ms
S.ToArray 753.51 ms

C1.ToList 32.06 ms
C1.ToArray 56.58 ms
S1.ToList 89.43 ms
S1.ToArray 132.85 ms

C2.ToList 3.45 ms
C2.ToArray 3.36 ms
S2.ToList 41.43 ms
S2.ToArray 40.84 ms

Group3:

C.ToList 756.64 ms
C.ToArray 771.56 ms
S.ToList 705.42 ms
S.ToArray 749.59 ms

C1.ToList 31.45 ms
C1.ToArray 57.03 ms
S1.ToList 91.26 ms
S1.ToArray 129.77 ms

C2.ToList 3.26 ms
C2.ToArray 3.29 ms
S2.ToList 41.57 ms
S2.ToArray 40.69 ms

Group4:

C.ToList 729.65 ms -> [749, 730, 721, 719, 723, 743, 721, 724, 727, 722, 716, 725, 723, 726, 718, 722, 731, 722, 723, 725, 723, 722, 728, 726, 728, 718, 726, 1088, 788, 737, 729, 710, 730, 728, 717, 723, 728, 721, 722, 728, 722, 736, 723, 729, 732, 724, 726, 727, 728, 728, 726, 726, 725, 727, 725, 728, 728, 718, 724, 725, 726, 724, 726, 729, 727, 722, 722, 725, 725, 728, 724, 727, 738, 717, 726, 723, 725, 725, 727, 724, 720, 726, 726, 723, 727, 730, 723, 721, 725, 727, 727, 733, 720, 722, 722, 725, 722, 725, 728, 726]
C.ToArray 788.36 ms -> [748, 740, 742, 797, 1090, 774, 781, 787, 784, 786, 786, 782, 781, 781, 784, 783, 783, 781, 783, 787, 783, 784, 775, 789, 784, 785, 778, 774, 781, 783, 786, 781, 780, 788, 778, 785, 777, 781, 786, 782, 781, 787, 782, 787, 784, 773, 783, 782, 781, 777, 783, 781, 785, 788, 777, 776, 784, 784, 783, 789, 778, 781, 791, 768, 779, 783, 781, 787, 786, 781, 784, 781, 785, 781, 780, 809, 1155, 780, 790, 789, 783, 776, 785, 783, 786, 787, 782, 782, 787, 777, 779, 784, 783, 776, 786, 775, 782, 779, 784, 784]
S.ToList 705.54 ms -> [690, 705, 709, 708, 702, 707, 703, 696, 703, 702, 700, 703, 700, 707, 705, 699, 697, 703, 695, 698, 707, 697, 711, 710, 699, 700, 708, 707, 693, 710, 704, 691, 702, 700, 703, 700, 705, 700, 703, 695, 709, 705, 698, 699, 709, 700, 699, 704, 691, 705, 703, 700, 708, 1048, 710, 706, 706, 692, 702, 705, 695, 701, 710, 697, 698, 706, 705, 707, 707, 695, 698, 704, 698, 699, 705, 698, 703, 702, 701, 697, 702, 702, 704, 703, 699, 707, 703, 705, 701, 717, 698, 695, 713, 696, 708, 705, 697, 699, 700, 698]
S.ToArray 745.01 ms -> [751, 743, 727, 734, 736, 745, 739, 750, 739, 750, 758, 739, 744, 738, 730, 744, 745, 739, 744, 750, 733, 735, 743, 731, 749, 748, 727, 746, 749, 731, 737, 803, 1059, 756, 769, 748, 740, 745, 741, 746, 749, 732, 741, 742, 732, 744, 746, 737, 742, 739, 733, 744, 741, 729, 746, 760, 725, 741, 764, 739, 750, 751, 727, 745, 738, 727, 735, 741, 720, 736, 740, 733, 741, 746, 731, 749, 756, 740, 738, 736, 732, 741, 741, 733, 741, 744, 736, 742, 742, 735, 743, 746, 729, 748, 765, 743, 734, 742, 728, 749]

C1.ToList 32.27 ms -> [36, 31, 31, 32, 31, 32, 31, 30, 32, 30, 30, 30, 34, 32, 31, 31, 31, 31, 31, 31, 31, 32, 38, 51, 68, 57, 35, 30, 31, 31, 30, 30, 33, 30, 31, 34, 31, 34, 32, 31, 31, 31, 31, 32, 30, 30, 31, 30, 31, 31, 32, 31, 31, 31, 32, 31, 31, 31, 32, 31, 33, 31, 31, 32, 30, 30, 30, 30, 30, 33, 30, 33, 32, 31, 30, 31, 31, 32, 32, 31, 35, 31, 34, 31, 31, 32, 31, 31, 32, 31, 32, 31, 31, 35, 31, 31, 31, 31, 31, 32]
C1.ToArray 56.72 ms -> [58, 56, 57, 57, 59, 58, 58, 57, 56, 59, 57, 55, 55, 54, 56, 55, 56, 56, 57, 59, 56, 55, 58, 56, 55, 55, 55, 55, 58, 58, 55, 57, 57, 56, 57, 57, 57, 57, 59, 59, 56, 57, 56, 57, 57, 56, 57, 59, 58, 56, 57, 57, 57, 58, 56, 56, 59, 56, 59, 57, 57, 57, 57, 59, 57, 56, 57, 56, 58, 56, 57, 56, 57, 59, 55, 58, 55, 55, 56, 56, 56, 56, 56, 56, 56, 56, 56, 57, 56, 56, 57, 56, 56, 57, 58, 57, 57, 57, 57, 57]
S1.ToList 90.72 ms -> [95, 90, 90, 89, 89, 89, 91, 89, 89, 87, 91, 89, 89, 89, 91, 89, 89, 89, 90, 89, 89, 90, 88, 89, 88, 90, 89, 90, 89, 89, 90, 90, 89, 89, 90, 91, 89, 91, 89, 90, 89, 89, 90, 91, 89, 89, 89, 89, 89, 89, 90, 89, 89, 89, 90, 89, 90, 89, 91, 89, 90, 89, 90, 89, 90, 89, 96, 89, 90, 89, 89, 89, 89, 89, 90, 89, 89, 89, 90, 87, 89, 90, 90, 91, 89, 91, 89, 89, 90, 91, 90, 89, 93, 144, 149, 90, 90, 89, 89, 89]
S1.ToArray 131.4 ms -> [130, 128, 127, 134, 129, 129, 130, 136, 131, 130, 132, 132, 133, 131, 132, 131, 133, 132, 130, 131, 132, 131, 130, 133, 133, 130, 130, 131, 131, 131, 132, 134, 131, 131, 132, 131, 132, 131, 134, 131, 131, 130, 131, 131, 130, 132, 129, 131, 131, 131, 132, 131, 133, 134, 131, 131, 132, 132, 131, 133, 131, 131, 130, 133, 131, 130, 134, 132, 131, 132, 132, 131, 131, 134, 131, 131, 132, 132, 131, 130, 138, 130, 130, 131, 132, 132, 130, 134, 131, 131, 132, 131, 130, 132, 133, 131, 131, 131, 130, 131]

C2.ToList 3.21 ms -> [4, 3, 3, 3, 4, 3, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 3, 3, 4, 3, 3, 3, 3, 3, 4, 3, 4, 3, 3, 3, 3, 3, 4, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 4, 3, 3, 3, 3, 3, 3, 4, 3, 4, 3, 3, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 4, 3, 3, 3]
C2.ToArray 3.22 ms -> [4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 4, 3, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 4, 3, 4, 3, 3, 3, 4, 3, 4, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 4, 3, 4, 3, 3, 3, 3, 3, 4, 3, 3, 3, 4, 3, 4, 3, 3, 3, 3, 4]
S2.ToList 41.46 ms -> [42, 40, 41, 40, 42, 40, 40, 40, 40, 40, 40, 40, 40, 41, 40, 40, 41, 40, 40, 40, 39, 41, 41, 39, 40, 40, 43, 40, 39, 40, 40, 40, 40, 40, 40, 41, 40, 40, 40, 43, 40, 43, 75, 76, 47, 39, 40, 40, 40, 40, 42, 40, 41, 40, 40, 40, 44, 41, 40, 42, 42, 40, 41, 41, 41, 41, 41, 40, 41, 41, 41, 41, 42, 41, 40, 41, 41, 42, 42, 41, 40, 41, 41, 41, 41, 41, 40, 42, 40, 42, 41, 41, 41, 43, 41, 41, 41, 41, 42, 41]
S2.ToArray 41.14 ms -> [42, 41, 41, 40, 40, 40, 40, 41, 41, 42, 41, 42, 41, 41, 41, 42, 41, 41, 42, 41, 41, 41, 41, 41, 42, 40, 41, 40, 42, 40, 42, 41, 40, 42, 41, 41, 43, 42, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 40, 40, 41, 41, 41, 40, 42, 41, 41, 41, 41, 41, 40, 41, 41, 42, 41, 41, 41, 42, 41, 41, 41, 41, 41, 41, 42, 42, 42, 41, 45, 46, 41, 40, 41, 41, 42, 41, 41, 41, 41, 41, 41, 40, 41, 43, 40, 40, 40, 40, 43, 41]

Group5:

C.ToList 757.06 ms -> [770, 752, 752, 751, 778, 763, 761, 763, 747, 758, 748, 747, 754, 749, 752, 753, 756, 762, 750, 753, 756, 749, 755, 757, 755, 756, 755, 744, 753, 758, 747, 751, 759, 751, 761, 755, 746, 752, 752, 749, 746, 752, 753, 755, 752, 755, 754, 754, 966, 937, 749, 759, 748, 747, 754, 749, 755, 750, 746, 754, 757, 752, 753, 745, 758, 755, 761, 753, 751, 755, 755, 752, 746, 756, 755, 746, 742, 751, 751, 749, 752, 751, 756, 756, 755, 742, 749, 754, 749, 756, 753, 751, 754, 752, 751, 754, 753, 749, 755, 756]
C.ToArray 772.8 ms -> [766, 772, 755, 763, 758, 767, 763, 762, 761, 768, 769, 763, 770, 757, 765, 760, 766, 759, 764, 761, 760, 777, 1102, 881, 759, 765, 758, 762, 772, 761, 758, 757, 765, 769, 769, 761, 762, 762, 763, 760, 770, 764, 760, 768, 758, 766, 763, 770, 769, 761, 764, 761, 761, 767, 761, 762, 764, 757, 765, 766, 767, 771, 753, 762, 769, 768, 759, 764, 764, 760, 763, 763, 763, 763, 763, 767, 761, 771, 760, 765, 760, 758, 768, 770, 751, 771, 767, 771, 765, 763, 760, 765, 765, 769, 767, 767, 1193, 774, 767, 764]
S.ToList 704.73 ms -> [682, 708, 705, 699, 705, 704, 695, 703, 702, 699, 701, 708, 699, 702, 703, 701, 701, 699, 701, 707, 707, 700, 701, 705, 700, 697, 706, 702, 701, 706, 699, 692, 702, 697, 707, 704, 697, 698, 699, 699, 702, 703, 698, 697, 702, 703, 702, 704, 694, 697, 707, 695, 711, 710, 700, 693, 703, 699, 699, 706, 698, 701, 703, 704, 698, 706, 700, 704, 701, 699, 702, 705, 694, 698, 709, 736, 1053, 704, 694, 700, 698, 696, 701, 700, 700, 706, 706, 692, 698, 707, 703, 695, 703, 699, 694, 708, 695, 694, 706, 695]
S.ToArray 744.17 ms -> [746, 740, 725, 740, 739, 731, 746, 760, 735, 738, 740, 734, 744, 748, 737, 744, 745, 727, 736, 738, 728, 743, 745, 735, 748, 760, 739, 748, 762, 742, 741, 747, 733, 746, 758, 742, 742, 741, 724, 744, 747, 727, 740, 740, 729, 742, 757, 741, 740, 742, 726, 739, 746, 1133, 749, 737, 730, 740, 747, 733, 747, 752, 731, 747, 742, 730, 741, 749, 731, 749, 743, 730, 747, 742, 731, 737, 745, 734, 739, 735, 727, 743, 752, 731, 744, 742, 729, 740, 746, 731, 739, 746, 733, 745, 743, 733, 739, 742, 727, 737]

C1.ToList 31.71 ms -> [35, 32, 32, 30, 31, 33, 31, 32, 32, 31, 31, 32, 32, 33, 32, 31, 31, 32, 31, 32, 32, 32, 31, 32, 33, 32, 31, 31, 31, 32, 31, 34, 31, 31, 32, 33, 32, 32, 31, 32, 34, 32, 31, 32, 33, 31, 32, 32, 31, 32, 32, 32, 32, 32, 32, 31, 31, 32, 31, 33, 30, 31, 32, 30, 30, 33, 32, 32, 34, 31, 31, 31, 31, 32, 31, 31, 31, 31, 32, 31, 31, 33, 31, 32, 32, 32, 33, 32, 31, 31, 31, 31, 31, 32, 32, 33, 32, 31, 31, 32]
C1.ToArray 59.53 ms -> [63, 57, 58, 58, 57, 59, 59, 57, 60, 131, 127, 67, 58, 56, 59, 56, 57, 58, 58, 58, 57, 59, 60, 57, 57, 59, 60, 57, 57, 57, 58, 58, 58, 58, 57, 57, 61, 57, 58, 57, 57, 57, 57, 57, 58, 58, 58, 58, 57, 58, 59, 57, 58, 57, 57, 59, 58, 58, 59, 57, 59, 57, 56, 56, 59, 56, 56, 59, 57, 58, 58, 58, 57, 58, 59, 59, 58, 57, 58, 62, 65, 57, 57, 57, 58, 60, 59, 58, 59, 57, 58, 57, 58, 59, 58, 58, 58, 59, 60, 58]
S1.ToList 82.78 ms -> [87, 82, 83, 83, 82, 82, 83, 84, 82, 83, 84, 84, 84, 82, 82, 84, 82, 84, 83, 84, 82, 82, 82, 81, 83, 83, 83, 84, 84, 82, 82, 83, 83, 83, 82, 83, 85, 83, 82, 82, 84, 82, 82, 83, 83, 83, 82, 82, 82, 83, 82, 83, 82, 84, 82, 83, 82, 83, 82, 82, 82, 84, 82, 83, 82, 82, 86, 83, 83, 82, 83, 83, 83, 82, 84, 82, 83, 81, 82, 82, 82, 82, 83, 83, 83, 82, 83, 84, 83, 82, 83, 83, 83, 82, 83, 84, 82, 82, 83, 83]
S1.ToArray 122.3 ms -> [122, 119, 119, 120, 119, 120, 120, 121, 119, 119, 122, 120, 120, 120, 122, 120, 123, 120, 120, 120, 121, 123, 120, 120, 120, 121, 120, 121, 122, 120, 123, 119, 121, 118, 121, 120, 120, 120, 119, 124, 119, 121, 119, 120, 120, 120, 120, 120, 122, 121, 123, 230, 203, 123, 119, 119, 122, 119, 120, 120, 120, 122, 120, 121, 120, 121, 120, 121, 120, 121, 120, 120, 120, 121, 122, 121, 123, 119, 119, 119, 119, 121, 120, 120, 120, 122, 121, 122, 119, 120, 120, 121, 121, 120, 121, 120, 121, 118, 118, 118]

C2.ToList 3.43 ms -> [5, 3, 4, 4, 4, 3, 4, 4, 4, 4, 4, 3, 3, 3, 4, 3, 3, 3, 4, 3, 4, 3, 4, 3, 4, 4, 3, 3, 3, 3, 4, 3, 3, 3, 4, 3, 4, 3, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 6, 4, 4, 3, 3, 4, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 3, 3, 3, 3, 4, 3, 4, 4, 4, 3, 4, 4, 3, 4, 4, 4, 4, 4, 4, 3, 3, 3, 4, 4, 3, 3, 3, 3]
C2.ToArray 3.48 ms -> [3, 3, 3, 3, 4, 4, 3, 4, 4, 4, 3, 4, 3, 3, 4, 3, 3, 4, 3, 4, 3, 3, 3, 4, 3, 3, 3, 4, 3, 3, 3, 3, 4, 3, 4, 3, 3, 4, 3, 4, 3, 3, 3, 3, 4, 3, 3, 3, 3, 4, 3, 4, 3, 3, 4, 3, 3, 4, 3, 3, 4, 4, 3, 3, 4, 3, 4, 4, 3, 4, 4, 4, 4, 4, 3, 3, 3, 4, 4, 3, 4, 4, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 4, 4, 4, 4, 3]
S2.ToList 41.47 ms -> [41, 41, 49, 67, 82, 41, 41, 40, 40, 40, 40, 40, 41, 40, 40, 40, 40, 40, 41, 40, 42, 42, 40, 40, 41, 41, 41, 40, 41, 40, 41, 40, 41, 40, 42, 41, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 41, 41, 41, 41, 41, 42, 41, 41, 41, 42, 40, 41, 40, 40, 40, 42, 40, 41, 42, 41, 42, 41, 42, 40, 41, 41, 41, 41, 41, 41, 41, 41, 40, 41, 40, 41, 41, 41, 40, 41, 41, 40, 40, 41, 41, 41, 41, 41, 43, 40, 40, 41, 42, 41]
S2.ToArray 40.62 ms -> [42, 41, 44, 40, 40, 40, 40, 41, 41, 40, 41, 41, 41, 40, 41, 41, 40, 41, 41, 40, 41, 40, 40, 41, 42, 41, 41, 41, 40, 40, 40, 40, 40, 41, 41, 42, 40, 41, 41, 41, 41, 41, 40, 42, 40, 40, 41, 41, 41, 40, 41, 40, 40, 40, 40, 40, 41, 40, 40, 41, 40, 40, 40, 40, 41, 40, 41, 41, 41, 40, 41, 41, 40, 41, 40, 41, 42, 40, 41, 41, 42, 41, 41, 40, 41, 40, 41, 40, 41, 41, 40, 40, 40, 41, 41, 40, 40, 40, 40, 40]

Из-за ограничения на количество символов в ответе stackoverflow образцы списков Group2 и Group3 опущены.

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

При обработке вычисляемых во время выполнения IEnumerable<T>объектов, если нагрузка, вызванная вычислениями, больше, чем выделение памяти и операции копирования ToListи ToArray, несоответствие незначительно ( C.ToList vs C.ToArrayиS.ToList vs S.ToArray ).

Разница может наблюдаться только для не рассчитанных во время выполнения IEnumerable<T>объектов ( C1.ToList vs C1.ToArrayи S1.ToList vs S1.ToArray). Но абсолютная разница (<60 мс) все еще приемлема для одного миллиона маленьких объектов IEnumerable<T>. В самом деле, разница решается реализации Enumerator<T>из IEnumerable<T>. Так что, если ваша программа действительно очень чувствительна к этому, вы должны профиль, профиль, профиль ! Наконец, вы, вероятно, обнаружите, что узкое место не в ToListили ToArray, но в деталях счетчиков.

И, результат C2.ToList vs C2.ToArrayи S2.ToList vs S2.ToArrayпоказывает, что вам действительно не нужно заботиться ToListили ToArrayоб ICollection<T>объектах, не рассчитанных во время выполнения .

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

Единственная причина, по которой вам нужно сделать выбор, заключается в том, что у вас есть особые потребности List<T>или T[], как описано в ответе @Jeppe Stig Nielsen .

qaqz111
источник
1

Для тех, кто заинтересован в использовании этого результата в другом Linq-to-sql, таких как

from q in context.MyTable
where myListOrArray.Contains(q.someID)
select q;

тогда генерируемый SQL будет одинаковым независимо от того, использовали ли вы List или Array для myListOrArray. Теперь я знаю, что некоторые могут спросить, почему даже перечислять перед этим оператором, но есть разница между SQL, сгенерированным из IQueryable vs (List или Array).

Gary
источник