Dichiarare una variabile all'interno o all'esterno di un ciclo foreach: quale è più veloce / migliore?


92

Quale di questi è il più veloce / migliore?

Questo:

List<User> list = new List<User>();
User u;

foreach (string s in l)
{
    u = new User();
    u.Name = s;
    list.Add(u);
}

O questo:

List<User> list = new List<User>();

foreach (string s in l)
{
    User u = new User();
    u.Name = s;
    list.Add(u);
}

Le mie capacità di sviluppo da principiante mi dicono che il primo è migliore, ma un mio amico mi dice che sbaglio, ma non può darmi una buona ragione per cui il secondo è migliore.

C'è qualche differenza nelle prestazioni?

Risposte:


112

Dal punto di vista delle prestazioni, entrambi gli esempi sono compilati nello stesso IL, quindi non c'è differenza.

Il secondo è migliore, perché esprime più chiaramente il tuo intento se uviene utilizzato solo all'interno del ciclo.


10
Si noti che v'è una differenza se la variabile viene catturato da un'espressione lambda o delegato anonimo; vedere Trappola variabile esterna .
dtb

Puoi spiegare perché entrambi sono compilati per lo stesso IL? Sono abbastanza certo che C # non sollevi le dichiarazioni di variabili all'inizio della funzione come fa javascript.
stile

4
@styfle ecco la risposta alla tua domanda.
David Sherret

I seguenti link Stack Overflow forniscono risposte più dettagliate: 1) Jon Hanna e 2) StriplingWarrior
user3613932

14

In ogni caso, il modo migliore sarebbe usare un costruttore che prende un Nome ... o, altrimenti, sfruttare la notazione tra parentesi graffe:

foreach (string s in l)
{
    list.Add(new User(s));
}

o

foreach (string s in l)
{
    list.Add(new User() { Name = s });
}

o ancora meglio, LINQ:

var list = l.Select( s => new User { Name = s});

Ora, mentre il tuo primo esempio potrebbe, in alcuni casi, essere impercettibilmente più veloce, il secondo è migliore perché è più leggibile e il compilatore può scartare la variabile (e ometterla del tutto) poiché non è utilizzata al di fuori foreachdell'ambito di.


6
Commento necrofilo della giornata: "o meglio ancora LINQ". Sicuramente è una riga di codice e questo ci fa sentire bene come sviluppatori. Ma la versione a quattro righe è MOLTO più comprensibile e quindi gestibile.
Oskar Austegard

5
Difficilmente. Con la versione LINQ so che quello che sto facendo è immutabile e funziona su ogni elemento.
Tordek

6

Una dichiarazione non causa l'esecuzione di alcun codice, quindi non è un problema di prestazioni.

Il secondo è ciò che intendi, ed è meno probabile che commetti un errore stupido se lo fai nel secondo modo, quindi usalo. Cerca sempre di dichiarare le variabili nel più piccolo ambito necessario.

Inoltre, il modo migliore è usare Linq:

List<User> users = l.Select(name => new User{ Name = name }).ToList();

2
Adoro la riga "Cerca sempre di dichiarare le variabili nel più piccolo ambito necessario". Penso che una sola riga possa rispondere molto bene alla domanda.
Manjoor

5

Ogni volta che hai una domanda sulle prestazioni, l'unica cosa da fare è misurare: fai un ciclo attorno al tuo test e calcola il tempo.

Per rispondere alla tua domanda - senza misurare :-) o guardare l'ilasm generato - nessuna differenza non sarebbe evidente in un numero significativo di iterazioni e l'operazione più costosa nel tuo codice è probabile che sia l'allocazione dell'utente per pochi ordini di grandezza, quindi concentrati sulla chiarezza del codice (come dovresti in generale) e vai con 2.

Oh, è tardi e immagino che sto solo cercando di dire di non preoccuparti di questo genere di cose o di lasciarti coinvolgere da dettagli come questo.

K


grazie per il suggerimento, penso che cronometrerò anche altre cose su cui mi sono ferito hehe: D
Marcus

Se vuoi andare oltre esaminando ciò che influisce sulle prestazioni, cerca di utilizzare un profiler del codice. Se non altro, inizierà a darti un'idea del tipo di codice e delle operazioni che richiedono più tempo. ProfileSharp ed EqatecProfilers sono gratuiti e sufficienti per iniziare.
Kevin Shea,


1

Tecnicamente, il primo esempio salverà alcuni nanosecondi perché lo stack frame non dovrà essere spostato per allocare una nuova variabile, ma questa è una quantità così piccola di tempo della CPU che non te ne accorgerai, se il compilatore non lo fa ottimizza comunque qualsiasi differenza.


Sono abbastanza sicuro che il CLR non alloca "una nuova variabile" su ogni iterazione di un ciclo.
dtb

Bene, il compilatore potrebbe ottimizzarlo, ma lo spazio dello stack deve essere allocato per qualsiasi variabile in un ciclo. Ciò dipenderà dall'implementazione e un'implementazione potrebbe semplicemente mantenere lo stesso frame dello stack, mentre un'altra (ad esempio Mono) potrebbe rilasciare lo stack e quindi ricrearlo su ogni loop.
Erik Funkenbusch

16
Tutte le variabili locali in un metodo (di primo livello o nidificate in un ciclo) vengono compilate in variabili a livello di metodo in IL. Lo spazio per le variabili viene allocato prima che il metodo venga eseguito, non quando viene raggiunto un ramo con la dichiarazione in C #.
dtb

1
@dtb Hai una fonte per questo reclamo?
stile

1

In questo scenario, la seconda versione è migliore.

In generale, se devi solo accedere al valore all'interno del corpo dell'iterazione, scegli la seconda versione. D'altra parte, se c'è uno stato finale la variabile manterrà oltre il corpo del ciclo, quindi dichiarare e utilizzare la prima versione.


0

Non dovrebbero esserci differenze percettibili nelle prestazioni.



0

Sono andato a verificare questo problema. Sorprendentemente ho scoperto durante i miei sporchi test che la seconda opzione è sempre leggermente più veloce.

namespace Test
{
  class Foreach
  {
    string[] names = new[] { "ABC", "MNL", "XYZ" };

    void Method1()
    {
      List<User> list = new List<User>();
      User u;

      foreach (string s in names)
      {
        u = new User();
        u.Name = s;
        list.Add(u);
      }
    }

    void Method2()
    {

      List<User> list = new List<User>();

      foreach (string s in names)
      {
        User u = new User();
        u.Name = s;
        list.Add(u);
      }
    }
  }

  public class User { public string Name; }
}

Ho verificato il CIL ma non è identico.

inserisci qui la descrizione dell'immagine

Quindi ho preparato qualcosa che volevo fosse molto meglio test.

namespace Test
{
  class Loop
  { 

    public TimeSpan method1 = new TimeSpan();
    public TimeSpan method2 = new TimeSpan();

    Stopwatch sw = new Stopwatch();

    public void Method1()
    {
      sw.Restart();

      C c;
      C c1;
      C c2;
      C c3;
      C c4;

      int i = 1000;
      while (i-- > 0)
      {
        c = new C();
        c1 = new C();
        c2 = new C();
        c3 = new C();
        c4 = new C();        
      }

      sw.Stop();
      method1 = method1.Add(sw.Elapsed);
    }

    public void Method2()
    {
      sw.Restart();

      int i = 1000;
      while (i-- > 0)
      {
        var c = new C();
        var c1 = new C();
        var c2 = new C();
        var c3 = new C();
        var c4 = new C();
      }

      sw.Stop();
      method2 = method2.Add(sw.Elapsed);
    }
  }

  class C { }
}

Anche in questo caso il 2 ° metodo era sempre vincente ma poi ho verificato il CIL non trovando differenze.

inserisci qui la descrizione dell'immagine

Non sono un guru della lettura CIL ma non vedo alcun problema di declinazione. Come già sottolineato, la dichiarazione non è assegnazione, quindi non è prevista alcuna penalità di prestazione.

Test

namespace Test
{
  class Foreach
  {
    string[] names = new[] { "ABC", "MNL", "XYZ" };

    public TimeSpan method1 = new TimeSpan();
    public TimeSpan method2 = new TimeSpan();

    Stopwatch sw = new Stopwatch();

    void Method1()
    {
      sw.Restart();

      List<User> list = new List<User>();
      User u;

      foreach (string s in names)
      {
        u = new User();
        u.Name = s;
        list.Add(u);
      }

      sw.Stop();
      method1 = method1.Add(sw.Elapsed);
    }

    void Method2()
    {
      sw.Restart();

      List<User> list = new List<User>();

      foreach (string s in names)
      {
        User u = new User();
        u.Name = s;
        list.Add(u);
      }

      sw.Stop();
      method2 = method2.Add(sw.Elapsed);
    }
  }

  public class User { public string Name; }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.