2014-07-12 5 views
10

Mentre rispondevo a una domanda dell'SO, mi è stato detto che la mia soluzione introdurrebbe una chiusura sulla variabile, quindi avrebbe prestazioni leggermente peggiori. Quindi la mia domanda è:"La chiusura su variabile offre prestazioni leggermente peggiori". Come?

  1. Come ci sarà una chiusura?
  2. In che modo influisce sulle prestazioni?

Ecco la question

List.Where(s => s.ValidDate.Date == DateTime.Today.Year).ToList(); 

Ecco il mio solution. Ho introdotto la variabile yr per l'anno di archiviazione.

int yr = DateTime.Now.Year; 
List.Where(s => s.ValidDate.Year == yr).ToList(); 

Qui è nella risposta è comments

+3

Questi due frammenti di codice hanno una semantica diversa. Se viene eseguito durante la notte di capodanno e l'anno cambia tra le varie operazioni, si ottengono risultati diversi. E alla fine - presumo - usare il codice di chiusura sarà più veloce, dal momento che non devi creare nuove istanze di 'DateTime' con ogni iterazione del ciclo e non devi accedere a due proprietà aggiuntive (che introducono un altro livello di indirezione). – knittl

+0

@knittl nella domanda originale è stato menzionato nei commenti. Ma dove viene introdotta la chiusura? – Sascha

+0

Beh, onestamente, la soluzione con la chiusura non dovrebbe essere più veloce? (Funziona in modo diverso -> come i 2 non sono equivalenti). Ma semmai dovrebbe essere più veloce? –

risposta

14

Prima di tutto, queste due soluzioni non sono funzionalmente equivalenti (se si fissa a confronto un appuntamento con un int (.Date == .Today.Year)):

  • Il primo snippet rivaluta DateTime.Today.Year per ogni valore dell'elenco, che può dare risultati diversi quando l'anno corrente cambia durante l'iterazione

  • Il secondo frammento memorizza l'anno corrente e lo riutilizza, quindi tutti gli elementi nell'elenco risultante avranno lo stesso anno. (Prenderò personalmente questo approccio, in quanto voglio assicurarmi che il risultato sia saggiato).

La chiusura viene introdotta perché lambda accede a una variabile dal suo ambito esterno, chiude il valore di yr. La compilazione C# genererà una nuova classe con un campo che contiene lo yr. Tutti i riferimenti a yr saranno sostituiti con il nuovo campo e l'originale yr non sarà nemmeno esiste nel codice compilato

dubito che ci sarà una riduzione delle prestazioni con l'introduzione di una chiusura. Se presente, il codice che utilizza la chiusura sarà più veloce, poiché non deve creare nuove istanze DateTime per ogni elemento della lista e quindi dereferenziare due proprietà. Deve solo accedere al campo della classe di chiusura generata dal compilatore che contiene il valore int dell'anno corrente. (Chi vuole confrontare il codice IL generato o il profilo dei due frammenti? :))

4

Ecco una misurazione ingenua del tempo, semplicemente per completare la risposta di knittl.

Il risultato è che la versione che valuta DateTime.Now ogni volta è più di 10 volte più lento di codice.

Risultati sulla mia macchina: T1: 8878 ms; T2: 589 ms. (Ottimizzazione massima, nessun debugger, ecc.).

class Program 
{ 
    static void Main(string[] args) 
    { 
     var things = new List<Something>(); 
     var random = new Random(111); 
     for (int i = 0; i < 100000; ++i) 
     { 
      things.Add(new Something(random.Next(2010, 2016))); 
     } 

     // to avoid measuring the JIT compilation and optimization time 
     T1(things); 
     T2(things); 

     var sw = Stopwatch.StartNew(); 
     for (int i = 0; i < 100; ++i) 
     { 
      T1(things); 
     } 
     Console.WriteLine(sw.ElapsedMilliseconds); 
     sw.Restart(); 
     for (int i = 0; i < 100; ++i) 
     { 
      T2(things); 
     } 
     Console.WriteLine(sw.ElapsedMilliseconds); 

     Console.ReadLine(); 
    } 

    private static void T1(List<Something> list) 
    { 
     var result = list.Where(x => x.ValidDate.Year == DateTime.Now.Year).ToList(); 
    } 

    private static void T2(List<Something> list) 
    { 
     var yr = DateTime.Now.Year; 
     var result = list.Where(x => x.ValidDate.Year == yr).ToList(); 
    } 
} 

class Something 
{ 
    public Something(int year) 
    { 
     this.ValidDate = new DateTime(year, 1, 1); 
    } 

    public DateTime ValidDate { get; private set; } 
} 
6

Oltre alla risposta di Knittl volevo tentare e misurare le prestazioni con e senza chiusura, ecco ciò che la mia prova assomiglia:

internal class SomeData { 
    public DateTime ValidDate { get; set; } 
    // other data ... 
} 

class Program { 
    static void Main(string[] args) { 
     var stopWatch = new Stopwatch(); 

     // Test with closure 
     IEnumerable<SomeData> data1 = CreateTestData(100000); 
     stopWatch.Start(); 
     int yr = DateTime.Now.Year; 
     List<SomeData> results1 = data1.Where(x => x.ValidDate.Year == yr).ToList(); 
     stopWatch.Stop(); 
     Console.WriteLine("With a closure - {0} ms", stopWatch.Elapsed.Milliseconds); 
     // ### Output on my machine (consistently): With a closure - 16 ms 

     stopWatch.Reset(); 

     // Test without a closure    
     IEnumerable<SomeData> data2 = CreateTestData(100000); 
     stopWatch.Start(); 
     List<SomeData> results2 = data2.Where(x => x.ValidDate.Year == DateTime.Today.Year).ToList(); 
     stopWatch.Stop(); 
     Console.WriteLine("Without a closure - {0} ms", stopWatch.Elapsed.Milliseconds); 
     // ### Output on my machine: Without a closure - 33 ms 
    } 

    private static IEnumerable<SomeData> CreateTestData(int numberOfItems) { 
     var dt = DateTime.Today; 
     for (int i = 0; i < numberOfItems; i++) { 
      yield return new SomeData {ValidDate = dt}; 
     } 
    } 
} 

Linea di fondo dal mio test - come mi aspettavo la versione con la chiusura è considerevolmente più veloce.

Problemi correlati