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

Почему AddRange быстрее, чем использование цикла foreach?

var fillData = new List<int>();
for (var i = 0; i < 100000; i++)
{
     fillData.Add(i);
}

var stopwatch1 = new Stopwatch();
stopwatch1.Start();
var autoFill = new List<int>();
autoFill.AddRange(fillData);
stopwatch1.Stop();

var stopwatch2 = new Stopwatch();
stopwatch2.Start();
var manualFill = new List<int>();
foreach (var i in fillData)
{
    manualFill.Add(i);
}
stopwatch2.Stop();

Когда я принимаю 4 результаты от stopwach1 и stopwach2, stopwatch1 всегда имеет меньшее значение, чем stopwatch2. Это означает, что addrange всегда быстрее, чем foreach. Кто-нибудь знает, почему?

4b9b3361

Ответ 1

Потенциально, AddRange может проверить, где переданное значение реализует IList или IList<T>. Если это так, он может узнать, сколько значений находится в диапазоне, и, следовательно, сколько места ему нужно выделить... тогда как цикл foreach может потребоваться перераспределить несколько раз.

Кроме того, даже после выделения List<T> может использовать IList<T>.CopyTo для выполнения массовой копии в базовом массиве (для диапазонов, которые реализуют IList<T>, из Конечно.)

Я подозреваю, что вы обнаружите, что если вы снова попробуете свой тест, но используете Enumerable.Range(0, 100000) для fillData вместо List<T>, то два будут занимать примерно одно и то же время.

Ответ 2

Если вы используете Add, он постепенно изменяет размер внутреннего массива по мере необходимости (удвоение), начиная с начального размера по умолчанию 10 (IIRC). Если вы используете:

var manualFill = new List<int>(fillData.Count);

Я ожидаю, что он радикально изменится (больше не изменится/копирование данных).

Из отражателя AddRange делает это внутренне, а не растет в удвоении:

ICollection<T> is2 = collection as ICollection<T>;
if (is2 != null)
{
    int count = is2.Count;
    if (count > 0)
    {
        this.EnsureCapacity(this._size + count);
        // ^^^ this the key bit, and prevents slow growth when possible ^^^

Ответ 3

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

Ответ 4

Разборка из отражателя для метода List AddRange имеет следующий код

ICollection<T> is2 = collection as ICollection<T>;
if (is2 != null)
{
    int count = is2.Count;
    if (count > 0)
    {
        this.EnsureCapacity(this._size + count);
        if (index < this._size)
        {
            Array.Copy(this._items, index, this._items, index + count, this._size - index);
        }
        if (this == is2)
        {
            Array.Copy(this._items, 0, this._items, index, index);
            Array.Copy(this._items, (int) (index + count), this._items, (int) (index * 2), (int) (this._size - index));
        }
        else
        {
            T[] array = new T[count];
            is2.CopyTo(array, 0);
            array.CopyTo(this._items, index);
        }
        this._size += count;
    }
}

Как вы видите, есть некоторые оптимизации, такие как вызов EnsureCapacity() и использование Array.Copy().

Ответ 5

При использовании AddRange коллекция может увеличить размер массива один раз, а затем скопировать значения в него.

Используя оператор foreach, коллекции необходимо увеличить размер коллекции более одного раза.

Увеличение размера thr означает копирование полного массива, требующего времени.

Ответ 6

Это похоже на то, как просить официанта десять раз выпить вам пива и попросить его немедленно принести вам 10 пива.

Как вы думаете, быстрее?)

Ответ 7

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

также могут быть некоторые оптимизации в реализации AddRange (например, memcpy)

Ответ 8

Попробуйте инициализировать емкость внутреннего списка перед добавлением вручную элементов:

var manualFill = new List<int>(fillData.Count); 

Ответ 9

Это потому, что цикл Foreach добавит все значения, которые цикл получает один раз, и
метод AddRange() соберет все значения, которые он получает как "кусок", и сразу добавит этот фрагмент в указанное место.

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

Ответ 10

Расширение AddRange не выполняет итерацию по каждому элементу, но применяет каждый элемент в целом. Оба foreach и .AddRange имеют цель. Addrange выиграет конкурс скорости в вашей текущей ситуации.