2009-08-19 16 views
5

Questa è più una domanda accademica sulla performance di un realistico "cosa dovrei usare" ma sono curioso visto che non mi diletto molto su IL per vedere cosa è costruito e non ho un grande set di dati su mano per il profilo contro.Quale è più veloce in un ciclo: chiamando una proprietà due volte o memorizzando la proprietà una volta?

Così che è più veloce:

List<myObject> objs = SomeHowGetList(); 
List<string> strings = new List<string>(); 
foreach (MyObject o in objs) 
{ 
    if (o.Field == "something") 
     strings.Add(o.Field); 
} 

o:

List<myObject> objs = SomeHowGetList(); 
List<string> strings = new List<string>(); 
string s; 
foreach (MyObject o in objs) 
{ 
    s = o.Field; 
    if (s == "something") 
     strings.Add(s); 
} 

tenere a mente che io non voglio davvero sapere l'impatto sulle prestazioni del string.Add (s) (come qualunque operazione debba essere eseguita non può essere realmente cambiata), solo la differenza di prestazioni tra l'impostazione di ogni iterazione (diciamo che s può essere qualsiasi tipo primitivo o stringa) i versi che chiamano il getter sull'oggetto ogni iterazione.

+19

Perché stai chiedendo _us_? Hai scritto il codice. Prendi un cronometro, fallo un miliardo di volte in entrambi i modi, e poi saprai quale è più veloce. –

risposta

9

La mia prima opzione è notevolmente più veloce nei miei test. Sono un flip flopper!Seriamente però, alcuni commenti sono stati fatti sul codice nel mio test originale. Ecco il codice aggiornato che mostra l'opzione 2 più veloce.

class Foo 
    { 
     public string Bar { get; set; } 

     public static List<Foo> FooMeUp() 
     { 
      var foos = new List<Foo>(); 

      for (int i = 0; i < 10000000; i++) 
      { 
       foos.Add(new Foo() { Bar = (i % 2 == 0) ? "something" : i.ToString() }); 
      } 

      return foos; 
     } 
    } 

    static void Main(string[] args) 
    { 

     var foos = Foo.FooMeUp(); 
     var strings = new List<string>(); 

     Stopwatch sw = Stopwatch.StartNew(); 

     foreach (Foo o in foos) 
     { 
      if (o.Bar == "something") 
      { 
       strings.Add(o.Bar); 
      } 
     } 

     sw.Stop(); 
     Console.WriteLine("It took {0}", sw.ElapsedMilliseconds); 

     strings.Clear(); 
     sw = Stopwatch.StartNew(); 

     foreach (Foo o in foos) 
     { 
      var s = o.Bar; 
      if (s == "something") 
      { 
       strings.Add(s); 
      } 
     } 

     sw.Stop(); 
     Console.WriteLine("It took {0}", sw.ElapsedMilliseconds); 
     Console.ReadLine(); 
    } 
+0

Ehi, +1 per scrivere un test! Ti darei +2 se potessi .. –

+0

Andy: su quale runtime/piattaforma hai eseguito il test? Inoltre, è una versione di rilascio? –

+0

Per me, il risultato è: 2294, 702 che contraddice la tua conclusione. –

7

La maggior parte delle volte il secondo snippet di codice deve essere almeno uguale a il primo snippet.

Questi due snippet di codice non sono funzionalmente equivalenti. Non è garantito che le proprietà restituiscano lo stesso risultato attraverso i singoli accessi. Di conseguenza, l'ottimizzatore JIT non è in grado di memorizzare il risultato nella cache (ad eccezione di casi banali) e sarà più veloce se si memorizza nella cache il risultato di una proprietà di lunga durata. Guarda questo esempio: why foreach is faster than for loop while reading richtextbox lines.

Tuttavia, per alcuni casi specifici come:

for (int i = 0; i < myArray.Length; ++i) 

dove myArray è un oggetto matrice, il compilatore è in grado di rilevare il modello e ottimizzare il codice e omettere i controlli associati. Potrebbe essere più lento se si cache il risultato di Length proprietà come:

int len = myArray.Length; 
for (int i = 0; i < myArray.Length; ++i) 
+0

Supponendo che questo compili, l'o.Field in entrambi i casi sarà di tipo stringa. Sto anche assumendo che il valore di o.Field, per ogni oggetto nella collezione, sia impostato su un valore significativo. Forse non capisco cosa intendi; Potresti essere più specifico? –

+0

Controlla il link nella mia risposta aggiornata. È un esempio specifico in cui fa una differenza significativa se si memorizza nella cache il valore restituito. –

2

Memorizzazione del valore in un campo è l'opzione più veloce.

Sebbene una chiamata al metodo non imponga un sovraccarico enorme, supera di gran lunga la memorizzazione del valore una volta su una variabile locale nello stack e quindi il recupero.

Io per primo lo faccio costantemente.

2

Generalmente il secondo è più veloce, in quanto il primo ricalcola la proprietà a ogni iterazione. Ecco un esempio di qualcosa che potrebbe prendere notevole quantità di tempo:

var d = new DriveInfo("C:"); 
d.VolumeLabel; // will fetch drive label on each call 
4

in realtà dipende l'attuazione. Nella maggior parte dei casi, si presume (come una questione di pratica comune/cortesia) che una proprietà è poco costoso. Tuttavia, è possibile che ogni "get" effettui una ricerca non memorizzata nella cache su alcune risorse remote. Per le proprietà standard e semplici, non noterai mai una vera differenza tra i due. Nel peggiore dei casi, fetch-once, store e ri-use saranno molto più veloci.

Sarei tentato di utilizzare lo get due volte finché non so che c'è un problema ... "ottimizzazione prematura", ecc ... Ma; se lo stavo usando in un loop stretto, quindi, potrei memorizzarlo in una variabile. Tranne Length su un array, che ha un trattamento speciale JIT ;-p

+0

@Marc: Il problema con il primo snippet di codice non è che 'o.Field' potrebbe effettivamente cambiare valore tra testarlo su" qualcosa "e aggiungerlo a' List'? 'o.Field ==" qualcosa "' potrebbe valutare true, ma quando si chiama 'strings.Add' si aggiunge" qualcos'altro "? –

+0

@Grant - oh assolutamente potrebbe, ma di nuovo sarebbe ... non standard - o almeno, dovrebbe essere ben documentato. Se è a causa del threading, ovviamente abbiamo solo noi stessi da incolpare. –

+0

@Marc: Non dico che si tratta di un'ottimizzazione prematura, specialmente quando si hanno a che fare con proprietà stupide non-O (1) (molte di esse esistono in WinForms) come quella che ho inserito nella mia risposta. Inoltre, in scenari multithreading, potresti preferire mantenere i risultati per motivi di correttezza. –

Problemi correlati