Я сделал некоторые тесты с типами коллекций, реализованными в .NET Framework.
Из справочного источника я знаю, что List<T>
использует массив для хранения содержимого. Чтобы избежать изменения размера массива при каждой вставке, длина массива удваивается каждый раз, когда свободное пространство заканчивается.
Теперь мой ориентир вставляет случайные значения long
в List
(см. рисунок выше для графика размера - времени). В размерах списков, таких как 128 или 256, есть очевидные "запаздывающие спайки", где необходимо перераспределить внутренний массив. Но при размере 512 (и 128, хотя?), Кажется, существует очень большое отставание, и время, затрачиваемое на вставку одного элемента, увеличивается.
В моем понимании график должен быть строго постоянным, за исключением случаев, когда внутренний массив необходимо перераспределить. Есть ли причины для такого поведения, возможно, связанные с фрагментацией управления памятью/памятью CLR или Windows?
Тесты были выполнены как 64-битное приложение на машине Windows 10/i7-3630QM (исходный код, как показано ниже). Поскольку одна операция добавления не измерима, я создаю 1000 списков и добавляю по одному элементу для каждого размера списка.
for (int i = 1; i <= MaxCollectionSize; i++)
{
// Reset time measurement
TestContainer.ResetSnapshot();
// Enable time measurement
TestContainer.BeginSnapshot();
// Execute one add operation on 1000 lists each
ProfileAction.Invoke(TestContainer);
TestContainer.EndSnapShot();
double elapsedMilliseconds = (TestContainer.GetElapsedMilliSeconds() / (double)Stopwatch.Frequency) * 1000;
// ...
}
EDIT: я дважды проверял результаты и да, они воспроизводимы. Я увеличил количество коллекций, проверенных с 1000 до 10000, и теперь результат стал намного более плавным (см. Изображение ниже). Шипы от изменения размера внутреннего массива теперь хорошо видны. И все же шаги на графике остаются - это расхождение с ожидаемой сложностью O (1), которая должна быть вставкой массива, если вы игнорируете изменение размера.
Я также пытался запускать коллекцию GC перед каждой операцией Add
, и график оставался точно таким же.
Что касается проблем создания объектов делегата: все мои делегаты (например, ProfileAction
) - это свойства экземпляра, которые остаются назначенными в течение одного полного цикла тестирования, в этом случае 10000 списков с 1000 операций добавления.