Список <T>.AddRange реализация неоптимальная - PullRequest
20 голосов
/ 23 января 2010

Профилирование моего приложения на C # показало, что значительное время затрачивается на 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) оправдывает снижение производительности при проверке и приведении типов во время выполнения. Но что может быть причиной трех строк, которые я указал (*)? Я думаю, что это может быть переписано в более быструю альтернативу:

is2.CopyTo(this._items, index);

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

Изменить:

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

Редактировать 2:

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

Ответы [ 3 ]

11 голосов
/ 23 января 2010

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

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

Редактировать: Часть, которую я нахожу странной, заключается в том, что вызовICollection<T>.CopyTo (копирование во временный массив) не происходит сразу после вызова EnsureCapacity.Если бы он был перемещен в это место, то после любого синхронного исключения список остался бы без изменений.Как есть, это условие выполняется только в том случае, если вставка происходит в конце списка.Причина в следующем:

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

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

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

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

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

Это не великие объяснения.

0 голосов
/ 23 января 2010

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

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

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

...