Implementacja listy <T> .AddRange jest nieoptymalna
Profilowanie mojej aplikacji C # wskazało, że spędzony jest znaczny czasList<T>.AddRange
. Użycie Reflectora do obejrzenia kodu w tej metodzie wskazało, że wywołujeList<T>.InsertRange
które jest realizowane jako takie:
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;
Można argumentować, że prostota interfejsu (mająca tylko jedno przeciążenie InsertRange) usprawiedliwia narzut wydajności związany z cheching i castingiem typu runtime. Ale co może być powodem 3 linii, które wskazałem(*)
? Myślę, że można go przepisać do szybszej alternatywy:
is2.CopyTo(this._items, index);
Czy widzisz jakiś powód, dla którego nie używasz tej prostszej i najwyraźniej szybszej alternatywy?
Edytować:
Dzięki za odpowiedzi. Tak więc zgodna opinia jest taka, że jest to środek zabezpieczający przed zbiorem danych wejściowych implementujących CopyTo w sposób wadliwy / złośliwy. Dla mnie wydaje się przesadą, aby stale płacić cenę 1) sprawdzanie typu runtime 2) dynamiczną alokację tymczasowej tablicy 3) podwoić operację kopiowania, kiedy to wszystko mogło zostać zapisane przez zdefiniowanie 2 lub kilku dodatkowych przeciążeń InsertRange , jeden dostajeIEnumerable
jak teraz, drugi dostajeList<T>
, trzeci dostanieT[]
. Późniejsze dwa mogły zostać zaimplementowane tak, aby działały dwa razy szybciej niż w bieżącym przypadku.
Edytuj 2:
Zaimplementowałem klasę FastList, identyczną z List, z tą różnicą, że zapewnia ona przeciążenie AddRange, która przyjmuje argument T []. To przeciążenie nie wymaga dynamicznej weryfikacji typu i podwójnego kopiowania elementów. Zrobiłem profil tego FastList.AddRange przeciwko List.AddRange, dodając tablice 4-bajtowe 1000 razy do listy, która początkowo była emtpy. Moja implementacja pokonuje prędkość standardowego List.AddRange ze współczynnikiem 9 (dziewięć!). List.AddRange zajmuje około 5% czasu wykonywania w jednym z ważnych scenariuszy użycia naszej aplikacji, zastępując List klasą zapewniającą szybszy AddRange może poprawić środowisko wykonawcze aplikacji o 4%.