Come ottimizzare le query iterabili con argomenti esterni

0
  • Userò C # qui come esempio, ma la mia domanda riguarda qualsiasi lingua.
  • La mia domanda va dal framework alla prospettiva del compilatore (la soluzione può essere implementando l'idea data all'interno del compilatore)

Considera tale codice:

if (sequence.Any()) ...

e diciamo che la sequenza "contiene" 1 milione di elementi. Questa condizione verrà comunque eseguita abbastanza velocemente (con un singolo controllo - o le mosse dell'iteratore o meno). Ora, leggera modifica:

if (sequence.Count() > 1) ...

ora la sequenza verrà ripetuta oltre 1 milione di elementi e dopo di ciò il risultato sarà "oh sì, abbiamo almeno 1 elemento".

Domanda: come potrebbe essere ottimizzato in tal modo, che le iterazioni eccessive non saranno fatte. D'altra parte vorrei evitare l'inquinante quadro con una pletora di metodi "ottimizzati" - CountAtMost, CountAtLeast - e così via.

Of source Count è solo un esempio, vengono in mente altre query di aggregazione: considera espressione ( esempio errato lo tengo per ragioni storiche) collection.Sum() > 1000 .

Non sto chiedendo delle ottimizzazioni specifiche per C #, la domanda è completamente generale - sequenze iterabili sono presenti in molte lingue. Le sequenze iterabili possono provenire anche dai generatori, quindi la domanda è come ottimizzare la query di aggregazione con argomenti esterni (confrontando con query).

    
posta greenoldman 21.01.2016 - 20:00
fonte

3 risposte

3

Penso di aver trovato la soluzione - la chiave è seguire l'esecuzione posticipata delle sequenze (iterabili). Count non deve restituire il tipo int , ma un certo tipo Counted che manterrebbe il riferimento alla sequenza. Con la conversione implicita in int si integrerebbe senza problemi, ma avendo sovraccaricato alcuni operatori poteva elaborare anche sequenze infinite.

    
risposta data 22.01.2016 - 08:08
fonte
1

Hai assolutamente bisogno di queste enormi raccolte nel tuo livello aziendale?

Da dove vengono queste enormi collezioni? Se, ad esempio, provengono da un database, potrebbe essere una buona idea filtrare / contare / sommare il risultato sul lato del database delle cose. Ciò consentirà di risparmiare un sacco di potenza di elaborazione e memoria.

Il tuo programma è assolutamente necessario per gestire queste enormi collezioni? In caso contrario, prova a ottimizzare le dimensioni delle tue raccolte prima che entrano nel tuo livello aziendale.

In ogni caso ...

Una risposta un po 'particolare per C #:

Se le prestazioni sono di enorme importanza, allora dovrà implementare alcuni di questi metodi, se è solo un "one-time" -thing, usa foreach con un count locale -variabile. Però; solo chiamare .Count() su una grande raccolta non è un'operazione enormemente pesante di per sé, purché l'enumerazione di ogni oggetto non richieda un'enorme quantità di potenza / tempo di elaborazione, nel qual caso dovresti probabilmente fare attenzione a come molti elementi che enumeri (vale a dire Take ). Pertanto, la creazione di metodi come questi potrebbe essere considerata micro-ottimizzazione, ovvero se la raccolta conterrà sempre 1 milione di elementi o meno.

Un altro modo per affrontare il problema è enumerare IEnumerable<T> in una raccolta di qualche tipo - List<T> , per esempio.

Ciò richiede solo l'enumerazione one , che fornirà le informazioni sulla raccolta, come .Count e l'accesso indicizzato agli elementi al suo interno.

Questo ovviamente dipende dal fatto che tu esegua o meno più operazioni sulla raccolta, poiché deve essere enumerato almeno una volta.

Cerca di essere pragmatico, poniti le semplici domande. Avrò bisogno di un accesso indicizzato agli articoli? Ho bisogno del conteggio? Devo assicurarmi che ci siano almeno quindici elementi? Avrò bisogno di eseguire più enumerazioni della collezione? Questa collezione sarà enorme? Quindi lavora semplicemente da lì, non esiste un modo magico corretto che sempre funzioni, devi assolutamente devi fare trade-off.

Ad un certo punto del codice, potrebbe essere una buona idea iterare solo 10 elementi, solo per verificare se contiene 10 o più oggetti, in un altro posto; chiamare Count() potrebbe essere un'idea migliore.
Ad un certo punto del codice, enumerare la tua collezione su List<T> potrebbe essere una buona idea, in altri punti - potrebbe non farlo.

Inoltre, C # ha già delle ottime funzionalità "voglio solo X" - si trova all'interno di Linq, ad esempio Take .

Esempio di codice:

if (hugeCollection.Take(4).Count() == 4)
{
    Foo();
}

Questo non enumera l'intera collezione, solo i primi 4 elementi, due volte . Quale per le piccole raccolte potrebbe non essere così eccezionale, ma per collezioni più grandi, certo.

Un tidbit C #:

Se .Any() o .Count() è più veloce dipende dal fatto che la chiamata sia effettuata su ICollection<T> o meno. Per ICollection<T> , Microsoft è andato avanti e l'ha ottimizzato , guardando il .Count -property; che è marginalmente più veloce di chiamare .Any() , indipendentemente dalle dimensioni delle raccolte, poiché la raccolta non dovrà mai essere enumerata.

    
risposta data 21.01.2016 - 21:06
fonte
0

Ok, quindi il codice per Count() in C # sarebbe:

public static int Count<T>(this IEnumerable<T> enumerable)
{
    var count = 0;

    using (var enumerator = enumerable.GetEnumerator())
    {
        while (enumerator.MoveNext())
            count += 1;
    }
}

È abbastanza facile immaginare che se questo viene sottolineato, il predicato viene inserito nella condizione while :

var result = false;
{
    var count = 0;

    using (var enumerator = enumerable.GetEnumerator())
    {
        while (!result && enumerator.MoveNext())
        {
            count += 1;
            result = count > x;
        }
    }
}

if (result) // the original condition featuring Count() > x

EDIT: questo non è possibile, a meno che il compilatore / ottimizzatore non possa ridistribuire IEnumerator<T> in quanto può avere effetti collaterali e il ciclo non può essere interrotto a meno che il compilatore non sia sicuro che nulla cambi da quello. < / edit >

Inoltre, temo che le opzioni di ottimizzazione siano limitate al runtime di lingua dato - JITed può sperare che il inline, compilato possa ottenere questa ottimizzazione attraverso l'analisi dell'intero programma dopo il collegamento e il funzionale - non c'è idea ...

Ma temo che il compilatore non sia in grado di fare molto, dato che solitamente verrà usato da binari esterni. E quelli vedono l'implementazione solo in link-time.

Se stai facendo un framework e vuoi questo, queste sono alcune idee:

  • Metti più attributi compilatore-in-linea-per-piacere che puoi
  • Crea due versioni: Count() e CountUntil(Predicate<int>)
  • Hai più speranze per i tuoi utenti:)

O possibilmente

  • Non implementare affatto Count() - fai in modo che gli utenti creino esplicitamente una raccolta e .Count o utilizzi foreach . Non molto amichevole però ...
risposta data 22.01.2016 - 00:14
fonte

Leggi altre domande sui tag