La definizione del profilo dell'applicazione C# indica che il tempo significativo è trascorso in List<T>.AddRange
. Utilizzando riflettore a guardare il codice in questo metodo indicato che chiama List<T>.InsertRange
che è implementato come tale:Elenco <T>. Aggiunta implementazione Raggio non ottimale
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;
Si può sostenere che la semplicità dell'interfaccia (solo avente uno sovraccarico InsertRange) giustifica l'overhead prestazioni di tipo di runtime cheching e casting. Ma quale potrebbe essere la ragione dietro le 3 linee che ho indicato con (*)
? penso che potrebbe essere riscritto per l'alternativa più veloce:
is2.CopyTo(this._items, index);
Non si vede alcuna ragione per non utilizzare questo semplice e apparentemente più veloce alternativa?
Edit:
Grazie per le risposte. Quindi l'opinione di consenso è che questa è una misura protettiva contro la raccolta di input che implementa il CopyTo in modo difettoso/malevolo. Per me sembra un eccesso di pagare costantemente il prezzo di 1) controllo del tipo di runtime 2) allocazione dinamica dell'array temporaneo 3) raddoppiare l'operazione di copia, quando tutto questo potrebbe essere stato salvato definendo 2 o qualche sovraccarico di InsertRange , uno ottenendo IEnumerable
come ora, il secondo ottenendo uno List<T>
, il terzo ottenendo T[]
. Gli ultimi due avrebbero potuto essere implementati per correre circa il doppio rispetto al caso corrente.
Edit 2:
feci implementare un elenco rapido classe, identica alla lista, tranne che fornisce anche un sovraccarico di AddRange che prende un argomento T []. Questo sovraccarico non richiede la verifica del tipo dinamico e la duplicazione di elementi. Ho profilato questo FastList.AddRange contro List.AddRange aggiungendo array di 4 byte 1000 volte a un elenco inizialmente emtpy. La mia implementazione batte la velocità di List.AddRange standard con un fattore di 9 (nove!). List.AddRange richiede circa il 5% del runtime in uno degli scenari di utilizzo importanti della nostra applicazione, sostituendo List con una classe che fornisce un AddRange più veloce potrebbe migliorare il runtime dell'applicazione del 4%.
@shojtsy: assicurati di aver catturato la mia modifica 2 :) –
Hai utilizzato uno scenario peggiore per il test. Prova a inserire un singolo 'int' con' Insert' invece di un array di 4 byte con 'InsertRange' e otterrai ancora più bump. –
Lo scenario testato è rappresentativo dell'utilizzo effettivo nella mia applicazione. L'elenco è un flusso di byte in cui gli array di 4-10 byte vengono aggiunti più volte. Comprendo che passare una semplice matrice a AddRange è dove le penalità prestazionali dell'implementazione standard sono più visibili. Questo era esattamente il mio punto. – shojtsy