LINQ richiede più cicli di elaborazione e memoria rispetto alle tecniche di iterazione dei dati di livello inferiore?

8

Sfondo

Recentemente sono in procinto di estenuanti interviste tecnologiche per posizioni che utilizzano lo stack .NET, alcuni dei quali includono domande stupide come questa , e alcune domande che sono più valide. Di recente mi sono imbattuto in un problema che potrebbe essere valido ma voglio verificarlo con la community qui per essere sicuro.

Quando ho chiesto ad un intervistatore come avrei calcolato la frequenza delle parole in un documento di testo e ho classificato i risultati, ho risposto che avrei

  1. Usa un oggetto flusso metti il file di testo in memoria come una stringa.
  2. Dividi la stringa in una matrice sugli spazi ignorando la punteggiatura.
  3. Usa LINQ contro l'array su .GroupBy() e .Count() , poi OrderBy() detto conteggio.

Ho sbagliato questa risposta per due motivi:

  1. Lo streaming di un intero file di testo nella memoria potrebbe essere disastroso. E se fosse stata un'intera enciclopedia? Invece dovrei eseguire lo streaming di un blocco alla volta e iniziare a costruire una tabella di hash.
  2. LINQ è troppo costoso e richiede troppi cicli di elaborazione. Avrei dovuto invece creare una tabella hash e, per ogni iterazione, aggiungere solo una parola alla tabella hash se non esisteva altrimenti e quindi incrementarne il conteggio.

La prima ragione sembra, beh, ragionevole. Ma il secondo mi dà più pausa. Ho pensato che uno dei punti di forza di LINQ è che semplicemente astrae le operazioni di livello inferiore come le tabelle hash ma che, sotto il velo, è sempre la stessa implementazione.

Domanda

A parte alcuni cicli di elaborazione aggiuntivi per chiamare qualsiasi metodo astratto, LINQ richiede significativamente più cicli di elaborazione per eseguire una determinata attività di iterazione dei dati rispetto a un'attività di livello inferiore (come la costruzione di una tabella hash ) sarebbe?

    
posta Matt Cashatt 14.04.2012 - 18:34
fonte

2 risposte

9

Direi che il principale punto debole di questa risposta è meno il suo uso di Linq e più gli specifici operatori scelti. GroupBy prende ciascun elemento e lo proietta su una chiave e un valore che va in una ricerca. In altre parole, ogni parola aggiungerà qualcosa alla ricerca.

L'implementazione naive .GroupBy(e => e) memorizzerà una copia di ogni parola nel materiale sorgente, rendendo la ricerca finale quasi grande quanto il materiale originale. Anche se proiettiamo il valore con .GroupBy(e => e, e => null) , creeremo una grande ricerca di valori piccoli.

Quello che vorremmo è un operatore che conserva solo le informazioni necessarie, che è una copia di ogni parola e il conteggio di quella parola finora. Per questo, possiamo usare Aggregate :

words.Aggregate(new Dictionary<string, int>(), (counts, word) => 
{
    int currentCount;
    counts.TryGetValue(word, currentCount);
    counts[word] = currentCount + 1;
    return counts;
} 

Da qui, ci sono diversi modi in cui possiamo tentare di farlo più velocemente:

  1. Invece di creare molte stringhe durante la suddivisione, possiamo passare attorno a strutture che fanno riferimento alla stringa originale e al segmento che contiene la parola e copiare solo il segmento quando risulta essere una chiave univoca
  2. Utilizzare Parallel Linq per aggregare su più core quindi combinare i risultati . Questo è banale rispetto al lavoro sulle gambe richiesto per farlo a mano.
risposta data 14.04.2012 - 20:06
fonte
6

Penso che tu abbia avuto una fuga limitata, l'intervistatore non sapeva davvero di cosa stesse parlando. Peggio ancora, crede che ci sia una risposta "giusta". Se fosse qualcuno per cui vorresti lavorare, mi aspetterei che prendesse la tua risposta iniziale, scoprisse il motivo per cui l'hai scelto e poi ti sfidi a migliorarlo se riesce a trovarlo.

LINQ spaventa le persone perché sembra magico. In realtà è molto semplice (in modo semplice sarebbe necessario essere un genio per inventarlo)

var result = from item in collection where item=>item.Property > 3 select item;

È compilato in:

IEnumerable<itemType> result = collection.Where(item=>item.property >3);

(Per favore non urlare se ho sbagliato la sintassi, è mezzanotte passata e sono a letto :))

Dov'è un metodo di estensione su IEnumerable che accetta un lambda. Il lambda è semplicemente (in questo caso) compilato da un delegato:

bool AMethod(ItemType item)
{
    return item.property >3;
}

Il metodo Where aggiunge semplicemente TUTTE le istanze di item in cui AMethod restituisce true a una raccolta che viene restituita.

Non c'è motivo che sia più lento di fare un foreach e aggiungere tutti gli elementi corrispondenti a una raccolta in quel ciclo. In effetti, il metodo di estensione Where sta probabilmente facendo proprio questo. La vera magia arriva quando devi iniettare un'alternativa dove criteri.

Come ho già detto nel mio commento, alcuni degli esempi collegati sono molto sbagliati. Ed è questa sorta di disinformazione che causa i problemi.

Infine, se l'intervista ti avesse dato una possibilità, avresti potuto dirlo:

  • LINQ è facile da leggere, specialmente quando inizi a introdurre proiezioni e raggruppamenti interessanti. Il codice facile da leggere è eas [y | ier] per mantenere il codice che è una vittoria.

  • Sarebbe davvero facile misurare le prestazioni se si trattasse effettivamente di un collo di bottiglia e sostituirlo con qualcos'altro.

risposta data 15.04.2012 - 01:32
fonte

Leggi altre domande sui tag