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

Список <T>.AddRange реализация субоптимальная

Профилирование моего приложения С# показало, что значительное время тратится на List<T>.AddRange. Использование Reflector для просмотра кода в этом методе показало, что он вызывает List<T>.InsertRange, который реализован как таковой:

public void InsertRange(int index, IEnumerable<T> collection)
{
    if (collection == null)
    {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection);
    }
    if (index > this._size)
    {
        ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_Index);
    }
    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;
        }
    }
    else
    {
        using (IEnumerator<T> enumerator = collection.GetEnumerator())
        {
            while (enumerator.MoveNext())
            {
                this.Insert(index++, enumerator.Current);
            }
        }
    }
    this._version++;
}

private T[] _items;

Можно утверждать, что простота интерфейса (только с одной перегрузкой InsertRange) оправдывает накладные расходы на производительность при зашивании и отливке типа времени выполнения. Но что может быть причиной 3-х строк, которые я указал с помощью (*)? Я думаю, что его можно было бы переписать на более быструю альтернативу:

is2.CopyTo(this._items, index);

Вы видите какую-либо причину не использовать эту более простую и, по-видимому, более быструю альтернативу?

Edit:

Спасибо за ответы. Таким образом, мнение консенсуса заключается в том, что это защитная мера против коллекции ввода, осуществляющей CopyTo, с дефектной/вредоносной манерой. Мне кажется, что избыточный уровень постоянно платит цену 1) проверка времени выполнения 2) динамическое распределение временного массива 3) удвоить операцию копирования, когда все это можно было бы сохранить, указав 2 или несколько перегрузок в InsertRange, один получает IEnumerable, как сейчас, второй получает a List<T>, третий получает T[]. Более поздние два могут быть реализованы для работы в два раза быстрее, чем в текущем случае.

Изменить 2:

Я реализовал класс FastList, идентичный List, за исключением того, что он также обеспечивает перегрузку AddRange, которая принимает аргумент T []. Эта перегрузка не требует проверки динамического типа и двойного копирования элементов. Я сделал профиль этого FastList.AddRange для List.AddRange, добавив 4-байтные массивы 1000 раз в список, который изначально был emtpy. Моя реализация превосходит скорость стандартного List.AddRange с коэффициентом 9 (девять!). List.AddRange занимает около 5% времени выполнения в одном из важных сценариев использования нашего приложения, заменяя List классом, обеспечивающим более быструю добавку, чтобы улучшить время выполнения приложений на 4%.

4b9b3361

Ответ 1

Они препятствуют реализации ICollection<T> доступа к индексам списка адресатов вне границ вставки. Выполнение выше приводит к IndexOutOfBoundsException, если вызывается ошибочная (или "манипулятивная" ) реализация CopyTo.

Имейте в виду, что T[].CopyTo в буквальном смысле внутренне реализовано как memcpy, поэтому накладные расходы на производительность при добавлении этой строки минута. Когда у вас есть такая низкая стоимость добавления безопасности на огромное количество вызовов, вы также можете это сделать.

Изменить: Я считаю странным тот факт, что вызов ICollection<T>.CopyTo (копирование во временный массив) происходит не сразу после вызова EnsureCapacity. Если он был перемещен в это место, то после любого синхронного исключения список остался бы неизменным. As-is, это условие выполняется только в том случае, если вставка происходит в конце списка. Обоснование здесь:

  • Все необходимое распределение происходит до изменения элементов списка.
  • Вызов Array.Copy не может завершиться неудачей, потому что
    • Память уже выделена
    • Оценки уже проверены
    • Типы элементов исходного и целевого массивов соответствуют
    • В С++ нет "конструктора копирования", это просто memcpy
  • Единственными элементами, которые могут генерировать исключение, является внешний вызов ICollection.CopyTo и распределения, необходимые для изменения размера списка и выделения временного массива. Если все три из них происходят до перемещения элементов для вставки, транзакция для изменения списка не может генерировать синхронное исключение.
  • Заключительное примечание: этот адрес строго исключительного поведения - вышеупомянутое обоснование не добавляет безопасности потоков.

Изменить 2 (ответ на редактирование OP): Профилировали ли вы это? Вы делаете смелые заявления о том, что Microsoft должна была выбрать более сложный API, поэтому вы должны убедиться, что вы верны в утверждениях о том, что текущий метод работает медленно. У меня никогда не было проблем с производительностью InsertRange, и я уверен, что любые проблемы с производительностью, с которыми кто-то сталкивается с этим, будут лучше решены с помощью редизайна алгоритма, чем путем переопределения динамического списка. Только так вы не считаете меня суровым негативным образом, помните следующее:

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

Ответ 2

Хороший вопрос, я изо всех сил пытаюсь найти причину. В справочном источнике нет намека. Одна из возможностей заключается в том, что они пытаются избежать проблемы, когда класс, реализующий объекты метода ICollection < > . CopyTo(), копирует в исходный индекс, отличный от нуля. Или как мера безопасности, предотвращающая беспорядочную сборку с элементами массива он не должен иметь доступ.

Другим является то, что это встречная мера, когда коллекция используется небезопасным образом. Если элемент добавлен в коллекцию другим потоком, это будет метод класса ColleTo(), который завершится неудачей, а не код Microsoft. Правильный человек получит услугу.

Это не большие объяснения.

Ответ 3

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

Это не очень хорошая идея, например, если автор структуры данных List определяет лучшую базовую структуру для хранения данных, чем массив, нет способа изменить реализацию List, поскольку вся коллекция ожидает массив в функция CopyTo.

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