2009-06-30 11 views
6

Lavoro su applicazioni sviluppate in C# /. NET con Visual Studio. Molto spesso ReSharper, nei prototipi dei miei metodi, mi consiglia di sostituire il tipo dei miei parametri di input con quelli più generici. Ad esempio, Elenco <> con IEnumerable <> se uso solo l'elenco con un foreach nel corpo del mio metodo. Posso capire perché sembra più intelligente scriverlo, ma sono piuttosto preoccupato per le prestazioni. Temo che le prestazioni delle mie applicazioni diminuirà se ascolto ReSharper ...Impatto sulle prestazioni del passaggio a interfacce generiche

Qualcuno può spiegare a me con precisione (più o meno) quello che succede dietro le quinte (cioè nel CLR) quando scrivo:

public void myMethod(IEnumerable<string> list) 
{ 
    foreach (string s in list) 
    { 
    Console.WriteLine(s); 
    } 
} 

static void Main() 
{ 
    List<string> list = new List<string>(new string[] {"a", "b", "c"}); 
    myMethod(list); 
} 

e qual è la differenza con:

public void myMethod(List<string> list) 
{ 
    foreach (string s in list) 
    { 
    Console.WriteLine(s); 
    } 
} 

static void Main() 
{ 
    List<string> list = new List<string>(new string[] {"a", "b", "c"}); 
    myMethod(list); 
} 
+2

Ok, so che questa è una discussione molto vecchia ma forse qualcosa lo colpisce come se stessi cercando aiuto sullo stesso problema. In effetti, c'è una penalità nelle prestazioni nell'uso delle interfacce e in un modo che non avevo mai immaginato. Metto una descrizione qui (mi dispiace: tedesco) nel mio BLOG: http://jochen.jochen-manns.de/index.php/2011/02/05/performance-von-schnittstellen-in-net-aka-the- good-the-bad-and-the-brutto/ –

+0

@JMS: mi è piaciuto quell'articolo (sfortunatamente non tutti saranno in grado di leggerlo dato che è in tedesco). Ciò dimostra che la complessità rende l'imprevedibilità. È anche un ottimo esempio di ciò che Jon Skeet chiama "dovresti scavare nei dettagli profondi della [...] confusa comprensione di JITting, thunking, vtables e come si applicano" ecc. – sehe

risposta

11

Sei preoccupato per le prestazioni, ma hai qualche motivo per quella preoccupazione? La mia ipotesi è che non hai affatto confrontato il codice. Punto di riferimento sempre prima di sostituire codice leggibile e pulito con un codice più performante.

In questo caso la chiamata a Console.WriteLine dominerà completamente le prestazioni comunque.

Mentre io sospetto che ci può essere una teorica differenza di prestazioni tra usando List<T> e IEnumerable<T> qui, ho il sospetto che il numero di casi in cui è significativo in applicazioni del mondo reale è irrisorio.

Non è nemmeno possibile utilizzare il tipo di sequenza per molte operazioni: c'è una singola chiamata allo GetEnumerator() che viene dichiarata per restituire lo IEnumerator<T> in ogni caso. Man mano che la lista diventa più grande, qualsiasi differenza nelle prestazioni tra i due otterrà anche più piccola, perché avrà solo un impatto proprio all'inizio del ciclo.

Ignorando tuttavia l'analisi, la cosa da prendere è misurare le prestazioni prima di basare le decisioni di codifica su di esso.

Per quanto riguarda ciò che accade dietro le quinte, dovresti scavare nei dettagli profondi di esattamente cosa c'è nei metadati in ciascun caso. Sospetto che, nel caso di un'interfaccia, ci sia un ulteriore livello di reindirizzamento, almeno in teoria, il CLR dovrebbe capire dove si trovava il tipo di oggetto target per IEnumerable<T> e quindi chiamare il codice del metodo appropriato. Nel caso di List<T>, il JIT conoscerebbe l'offset corretto nel vtable per iniziare, senza la ricerca aggiuntiva. Questo si basa solo sulla mia comprensione un po 'confusa di JITting, thunking, vtables e come si applicano alle interfacce. Potrebbe essere leggermente sbagliato, ma soprattutto è un dettaglio di implementazione.

+0

Infatti, in particolare nelle app del mondo reale, dove se si sta facendo qualsiasi cosa con reti, database o dischi, è molto improbabile che qualcosa del genere possa causare problemi alle prestazioni. –

+0

ok forse il fatto di dare un esempio concreto nasconde la mia vera domanda. E forse la mia domanda non era quella giusta. In realtà sono più curioso del meccanismo del CLR che della performance in sé; Ovviamente quando scrivi tonnellate di codice, l'uso sistematico della maggior parte dei tipi e dell'interfaccia generici non dovrebbe essere la cosa che costa di più. – PierrOz

+0

In realtà, è possibile ottenere un aumento delle prestazioni quando si utilizzano le interfacce. Come CLR che esegue il caching sulle chiamate di interfaccia, cosa che non funzionerà con le normali chiamate. – DiVan

1

In generale, la maggiore flessibilità varrà ciò che le prestazioni minore differenza subirebbe.

1

Il motivo di base per questo suggerimento è la creazione di un metodo che funziona su IEnumberable vs. List è la flessibilità futura. Se in futuro è necessario creare un MySpecialStringsCollection, si potrebbe avere implementare il metodo IEnumerable e utilizzare ancora lo stesso metodo.

In sostanza, penso che tutto ciò si riduce, a meno che non si noti un colpo significativo e significativo sulle prestazioni (e sarei scioccato se ne notaste qualcuno); preferisci un'interfaccia più tollerante, che accetterà più di quello che ti aspetti oggi.

3

Si dovrebbe guardare il codice generato per essere certi, ma in questo caso, dubito che ci sia molta differenza. L'istruzione foreach opera sempre su un oggetto IEnumerable o IEnumerable<T>. Anche se si specifica List<T>, sarà comunque necessario ottenere lo IEnumerable<T> per l'iterazione.

+2

In realtà la dichiarazione foreach * non 't * richiede un IEnumerable o IEnumerable per funzionare. Il tipo deve semplicemente avere un metodo GetEnumerator() che restituisce qualcosa con un metodo MoveNext() appropriato e la proprietà Current. Ma in questo caso, naturalmente, List * fa * implement IEnumerable . –

+0

Inoltre, se l'utente ha un modo migliore di fare le cose se viene fornito un elenco o Collezione , è libero di verificare se l'IEnumerable è una di quelle classi e casi particolari per migliorare le prestazioni. – user7116

+0

Anche se di solito sarebbe meglio controllare per IList e ICollection :) –

1

Nella prima versione (IEnumerable) è più generico e in realtà si dice che il metodo accetta qualsiasi argomento che implementa questa interfaccia.

Seconda versione yo limita il metodo per accettare un tipo di classe specifico e questo non è raccomandato. E la performance è per lo più la stessa.

0

Un'interfaccia definisce semplicemente la presenza e la firma di metodi e proprietà pubblici implementati dalla classe. Poiché l'interfaccia non "sta in piedi da sola", ci dovrebbe essere no differenza di prestazioni per il metodo stesso, e qualsiasi penalità di "casting" - se presente - dovrebbe essere quasi troppo piccola per essere misurata.

1

La definizione di List<T> è:

[SerializableAttribute] 
public class List<T> : IList<T>, ICollection<T>, 
    IEnumerable<T>, IList, ICollection, IEnumerable 

Così List<T> è derivato da IList, ICollection, IList<T>, e ICollection<T>, oltre a IEnumerable e IEnumerable<T>.

L'interfaccia IEnumerable espone il metodo GetEnumerator che restituisce un IEnumerator , un metodo MoveNext e una proprietà Current. Questi meccanismi sono ciò che la classe List<T> utilizza per scorrere l'elenco con foreach e next.

Ne consegue che, se IList, ICollection, IList<T>, and ICollection<T> non è richiesto per eseguire il lavoro, è quindi preferibile utilizzare IEnumerable o IEnumerable<T>, eliminando in tal modo l'impianto idraulico aggiuntivo.

0

Non ci sono penalità di prestazioni per un upcast statico. È un costrutto logico nel testo del programma.

Come altre persone hanno detto, l'ottimizzazione prematura è la radice di tutto il male. Scrivi il tuo codice, eseguilo attraverso un'analisi dell'hotspot prima di preoccuparti delle prestazioni di ottimizzazione delle cose.

0

Ottenere in IEnumerable <> potrebbe creare dei problemi, poiché potresti ricevere alcune espressioni LINQ con esecuzione diversa o rendimento restituito. In entrambi i casi non si avrà una collezione ma qualcosa che potrebbe essere iterato. Quindi, quando si desidera impostare dei limiti, è possibile richiedere un array. Non c'è alcun problema nel chiamare la collezione .ToArray() prima di passare il parametro, ma sarai sicuro che non ci siano caveat nascosti differenti.

2

In generale, direi che se si è sostituire l'interfaccia non generico equivalente dal sapore generico (ad esempio IList<> ->IList<T>) si sono tenuti a ottenere una migliore o equivalente prestazioni.

Un punto vendita unico è quello perché, a differenza di java,.NET non usa la cancellazione del tipo e supporta i tipi di valore reale (struct), una delle differenze principali sarebbe nel modo in cui memorizza, ad es. a List<int> internamente. Questo potrebbe diventare abbastanza rapidamente una grande differenza a seconda di quanto sia intensamente utilizzato l'elenco.


Un benchmark sintetico braindead mostrato:

for (int j=0; j<1000; j++) 
    { 
     List<int> list = new List<int>(); 
     for (int i = 1<<12; i>0; i--) 
      list.Add(i); 

     list.Sort(); 
    } 

essere più veloce di un fattore di 3.2x rispetto alla semi-equivalente non generico:

for (int j=0; j<1000; j++) 
    { 
     ArrayList list = new ArrayList(); 
     for (int i = 1<<12; i>0; i--) 
      list.Add(i); 

     list.Sort(); 
    } 

Disclaimer Mi rendo conto che questo benchmark è sintetico, in realtà non si concentra sull'uso di interfacce proprio lì (invia piuttosto direttamente chiamate di metodi virtuali su un tipo specifico) ecc. Tuttavia, illustra il punto che sto facendo. Non temere i generici (almeno non per motivi di prestazioni).

Problemi correlati