2010-01-23 11 views
20

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%.

+0

@shojtsy: assicurati di aver catturato la mia modifica 2 :) –

+0

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. –

+0

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

risposta

11

Impediscono l'implementazione di ICollection<T> dall'accesso agli indici dell'elenco di destinazione al di fuori dei limiti di inserimento. L'implementazione sopra comporta un IndexOutOfBoundsException se viene chiamata un'implementazione difettosa (o "manipolativa") di CopyTo.

Ricordare che lo T[].CopyTo è letteralmente implementato internamente come memcpy, quindi l'overhead delle prestazioni di aggiunta di quella linea è minuto. Quando si ha un costo così basso da aggiungere sicurezza a un numero enorme di chiamate, si potrebbe anche farlo.

Edit: La parte che trovo strano è il fatto che la chiamata a ICollection<T>.CopyTo (copia per l'array temporaneo) non si verifica subito dopo la chiamata a EnsureCapacity. Se fosse spostato in quella posizione, quindi seguendo qualsiasi synchronous exception l'elenco rimarrebbe invariato. Così com'è, questa condizione vale solo se l'inserimento avviene alla fine dell'elenco. Il ragionamento qui è:

  • Tutte le allocazioni necessarie avvengono prima di modificare gli elementi dell'elenco.
  • Le chiamate verso Array.Copy non possono fallire perché
    • La memoria è già allocato
    • I limiti sono già controllati
    • I tipi di elementi delle matrici di origine e destinazione corrispondono
    • Non c'è "costruttore di copie "usato come in C++ - è solo una memcpy
  • Gli unici elementi che possono generare un'eccezione sono la chiamata esterna a ICollection.CopyTo e le allocazioni richieste per ridimensionare l'elenco e allocare l'array temporaneo. Se tutti e tre si verificano prima di spostare elementi per l'inserimento, la transazione per modificare l'elenco non può generare un'eccezione sincrona.
  • Nota finale: Questo indirizzo di comportamento rigorosamente eccezionale - la motivazione di cui sopra non aggiungere thread-sicurezza.

Edit 2 (risposta alla modifica del PO): Avete profilata questo? Stai sostenendo con audacia che Microsoft avrebbe dovuto scegliere un'API più complicata, quindi dovresti assicurarti di essere corretto nelle asserzioni secondo cui il metodo attuale è lento. Non ho mai avuto problemi con le prestazioni di InsertRange e sono abbastanza sicuro che qualsiasi problema di prestazioni che qualcuno dovrà affrontare sarà risolto meglio con una riprogettazione dell'algoritmo piuttosto che reimplementando la lista dinamica.Solo così si non mi prendi come dura in modo negativo, tenere presente quanto segue:

  • I non voglio non può sopportare la gente del mio team di sviluppo che, come per reinvent the square wheel.
  • I sicuramente desidera che le persone della mia squadra si interessino a potenziali problemi di prestazioni e pongono domande sugli effetti collaterali che il loro codice potrebbe avere. Questo punto vince quando è presente - ma finché le persone fanno domande li guiderò a trasformare le loro domande in risposte solide. Se riesci a dimostrarmi che un'applicazione ottiene un vantaggio significativo attraverso ciò che inizialmente sembra essere una cattiva idea, allora questo è solo il modo in cui le cose vanno a volte.
+0

L'allocatore CLR è estremamente veloce, inoltre è probabile che l'array temporaneo venga raccolto prima di raggiungere Gen 1, quindi non dovrebbe causare problemi. –

2

È una buona domanda, sto lottando per trovare una ragione. Non c'è alcun suggerimento nella Sorgente di Riferimento. Una possibilità è che tentano di evitare un problema quando la classe che implementa gli oggetti del metodo ICollection <> .CopyTo() contro la copia su un indice di inizio diverso da 0. O come misura di sicurezza, impedendo alla raccolta di fare scherzi con gli elementi dell'array non dovrebbe avere accesso a

Un altro è che si tratta di una contromisura quando la raccolta viene utilizzata in modo non sicuro. Se un elemento viene aggiunto alla raccolta da un altro thread, sarà il metodo CopyTo() della classe di raccolta che non riesce, non il codice Microsoft. La persona giusta riceverà la chiamata di servizio.

Queste non sono grandi spiegazioni.

+0

I membri di 'Lista ' sono esplicitamente dichiarati di non essere thread-safe. Finché 'Lista ' e la raccolta di elementi da inserire viene sincronizzata manualmente, le chiamate a' CopiaTo' non possono mai verificarsi in modo pericoloso. –

+1

Sì, ma non era quello il punto. Il punto era: chi riceve la telefonata quando esplode. Una vera preoccupazione per un'azienda come Microsoft. Questo hotfix è un buon esempio, in realtà è un'eccezione attivata dal codice del client che si dimentica di bloccare: http://support.microsoft.com/kb/831730 –

+0

Nel caso precedente, né il codice originale né la modifica proposta sono più thread - Sicuro rispetto agli altri. Una cosa strana è il fatto che la chiamata a "ICollection .CopyTo' non si trova immediatamente dopo la chiamata a' CleanCapacity' - se fosse su una qualsiasi eccezione sincrona, l'elenco rimarrebbe invariata dall'operazione. –

0

C'è un problema con la soluzione se ci pensate per un minuto, se si cambia il codice in questo modo si sta essenzialmente dando alla collezione che dovrebbe essere aggiunto l'accesso a una infrastruttura interna.

Questa non è una buona idea, ad esempio se l'autore della infrastruttura List rileva una struttura sottostante migliore per archiviare i dati di una matrice non c'è modo di modificare l'implementazione di List poiché tutta la collezione si aspetta un array nella funzione CopyTo.

In pratica si sta cementando l'implementazione della classe List, anche se la programmazione orientata agli oggetti ci dice che l'implementazione interna di una infrastruttura deve essere qualcosa che può essere modificata senza rompere altri codici.

+0

-1: sta parlando dell'implementazione interna esistente. Tale implementazione si basa già su un oggetto che implementa 'ICollection ' per implementare correttamente il membro 'CopyTo' di tale interfaccia. L'alternativa suggerita non aggiunge né rimuove da quell'elenco di ipotesi. –

+0

Questo è vero, ma non cambia il fatto che il suo suggerimento rompe ancora l'incapsulamento se la struttura dei dati interni è e fa affidamento su una corretta implementazione della raccolta in entrata. Non ci sono garanzie che la raccolta che viene aggiunta alla lista abbia una buona o veloce implementazione di CopyTo, ancor peggio potrebbe avere un'implementazione che ora ha accesso ai dati interni della lista che altrimenti non avrebbe e non dovrebbe avere e può utilizzare per rubare dati a cui non dovrebbe avere accesso. Credo che il mio punto sia ancora valido. –

+0

La tua dichiarazione iniziale era corretta, ma il ragionamento che hai dato nel 2 ° e 3 ° paragrafo non sarebbe un motivo per giungere a tale conclusione. Inoltre, devi lavorare sul presupposto che l'implementazione di 'ICollection .CopyTo' sia corretta - ecco perché esistono interfacce standard.Il ragionamento è semplicemente che un obiettivo primario nelle VM gestite è ottenere un'eccezione fuori limite per l'accesso illegale alla memoria, e questo è un modo economico per farlo. –

Problemi correlati