Prestazioni “nested foreach” vs “lambda / linq query” (LINQ-to-Objects)

In termini di prestazioni, cosa dovresti usare “Nested foreach‘s” o “lambda / linq queries”?

Scrivi il codice più chiaro che puoi, quindi fai un benchmark e un profilo per scoprire eventuali problemi di prestazioni. Se si verificano problemi di prestazioni, è ansible sperimentare con codice diverso per capire se è più veloce o meno (misurando tutto il tempo con dati quanto più realistici ansible) e quindi fare un giudizio su se il miglioramento delle prestazioni vale la leggibilità colpire.

In molti casi, un approccio diretto per la foreach sarà più veloce di LINQ. Ad esempio, considera:

 var query = from element in list where element.X > 2 where element.Y < 2 select element.X + element.Y; foreach (var value in query) { Console.WriteLine(value); } 

Ora ci sono due clausole where e una clausola select , quindi ogni object finale deve passare attraverso tre iteratori. (Ovviamente le due clausole where potrebbero essere combinate in questo caso, ma sto facendo un punto generale.)

Ora confrontalo con il codice diretto:

 foreach (var element in list) { if (element.X > 2 && element.Y < 2) { Console.WriteLine(element.X + element.Y); } } 

Ciò funzionerà più velocemente, perché ha meno cerchi da percorrere. È probabile che l'output della console ridurrà il costo dell'iteratore, e preferirei sicuramente la query LINQ.

EDIT: per rispondere ai loop "nested foreach" ... in genere quelli sono rappresentati con SelectMany o una seconda from clausola:

 var query = from item in firstSequence from nestedItem in item.NestedItems select item.BaseCount + nestedItem.NestedCount; 

Qui stiamo aggiungendo solo un singolo iteratore in più, perché già nella prima sequenza useremo un iteratore aggiuntivo per elemento a causa del ciclo foreach nidificato. C'è ancora un po 'di overhead, incluso il sovraccarico di fare la proiezione in un delegato invece di "inline" (qualcosa che non ho menzionato prima) ma non sarà ancora molto diverso dalle prestazioni nested-foreach.

Questo non vuol dire che non puoi spararti ai piedi con LINQ, ovviamente. È ansible scrivere query incredibilmente inefficienti se non si impegnano prima il cervello, ma questo è tutt'altro che esclusivo per LINQ ...

Se fate

 foreach(Customer c in Customer) { foreach(Order o in Orders) { //do something with c and o } } 

Eseguirai Customer.Count * Order.Countations


Se fate

 var query = from c in Customer join o in Orders on c.CustomerID equals o.CustomerID select new {c, o} foreach(var x in query) { //do something with xc and xo } 

Eseguirai le iterazioni Customer.Count + Order.Count, perché Enumerable.Join è implementato come HashJoin.

È più complesso su questo. In definitiva, gran parte di LINQ-to-Objects è (dietro le quinte) un ciclo foreach , ma con l’overhead aggiunto di un po ‘di astrazione / iteratore blocchi / ecc. Tuttavia, a meno che non facciate cose molto diverse nelle due versioni (foreach vs LINQ ), dovrebbero essere entrambi O (N).

La vera domanda è: c’è un modo migliore di scrivere il tuo algoritmo specifico che significa che foreach sarebbe inefficiente? E LINQ può farlo per te?

Ad esempio, LINQ semplifica l’hash / raggruppa / ordina i dati.

È stato detto prima, ma merita di essere ripetuto.

Gli sviluppatori non sanno mai dove si trova il collo di bottiglia delle prestazioni fino a quando non eseguono i test delle prestazioni.

Lo stesso vale per il confronto tra la tecnica A e la tecnica B. A meno che non ci sia una differenza drammatica, devi solo provarla. Potrebbe essere ovvio se hai uno scenario O (n) vs O (n ^ x), ma dal momento che la roba LINQ è per lo più stregoneria del compilatore, merita una profilazione.

Inoltre, a meno che il tuo progetto non sia in produzione e tu abbia profilato il codice e scoperto che quel loop sta rallentando l’esecuzione, lasciala come se fosse la tua preferenza per la leggibilità e la manutenzione. L’ottimizzazione prematura è il diavolo.

Un grande vantaggio è che l’utilizzo delle query Linq-To-Objects ti dà la possibilità di trasformare facilmente la query su PLinq e fare in modo che il sistema esegua automaticamente l’operazione sul numero corretto di thread per il sistema corrente.

Se stai usando questa tecnica su grandi set di dati, è facile diventare una grande vittoria per pochissimi problemi.