2012-07-02 19 views
5

Recentemente ho avviato un'applicazione WPF. L'ho collegato a un database BaseX (basato su XML) e recuperato circa un milione di voci da esso. Volevo iterare le voci, calcolare qualcosa per ogni voce e poi scrivere che nel database:Iterazione su grandi raccolte in C#: impiega molto tempo

IEnumerable<Result> resultSet = baseXClient.Query("...", "database"); 
foreach (Result result in resultSet) 
{ 
    ... 
} 

Il problema: L'interno del foreach non viene mai raggiunto. il metodo Query() ritorna abbastanza veloce, ma quando viene raggiunto il foreach C# sembra fare SOMETHING con la collezione, il codice non continua per un tempo molto lungo (almeno 10 minuti, non lasciarlo mai più funzionare). Cosa sta succedendo qui? Ho provato a limitare il numero di elementi recuperati. Quando si recuperano 100.000 risultati, si verifica la stessa cosa ma il codice continua dopo circa 10-20 secondi. Quando recuperi l'intero milione di risultati, C# sembra essere bloccato per sempre ...

Qualche idea? saluti

Modifica: perché questo sta accadendo Come alcuni di voi hanno sottolineato, la ragione di questo comportamento sembra essere che la query è in realtà valutata solo quando MoveNext() sul Enumeratore all'interno del Enumerable si chiama. Il mio database sembra non essere in grado di restituire un valore alla volta, ma restituisce invece l'intero milione di set di dati contemporaneamente. Cercherò di passare ad un altro database (Apache Lucene, se possibile, dato che ha un buon supporto per la ricerca fulltext) e modificare questo post per farti sapere se ha cambiato qualcosa.
PS: Sì, sono consapevole che un milione di risultati è molto. Questo non è pensato per l'uso dal vivo, è solo un passo per la preparazione dei dati. Mentre non mi aspettavo che il codice potesse essere eseguito in pochi secondi, ero ancora sorpreso di vedere prestazioni scadenti nel database.

Modifica: la soluzione Così ho migrato il database XML ad Apache Lucine. Funziona come un fascino! Ovviamente Lucine è un database testuale che non è adatto per ogni caso d'uso, ma per me ha funzionato a meraviglia. Può iterare oltre un milione di voci in pochi secondi, viene recuperata una voce per ciclo - funziona molto bene!

+7

[esecuzione differita] (http://blogs.msdn.com/b/charlie/archive/2007/12/09/deferred-execution.aspx). – Adam

+0

@codesparkle, buone informazioni, ma mi chiedo perché sembra che la sua intera query debba essere eseguita prima che il primo elemento venga restituito. –

+1

Si prega di prendere in considerazione la pubblicazione di più codice e un campione minore dei dati. – EKS

risposta

3

Un milione di qualsiasi cosa è molto ... quindi qualsiasi operazione che ottiene molti elementi richiede un tempo considerevole. Sembra che la libreria che usi non differisca il recupero degli oggetti finché non sia assolutamente necessario, quindi vedi l'impatto di ottenere tutti gli oggetti nascosti dietro la frase "foreach".

Cosa accade:

"foreach" non è una singola operazione, ma piuttosto diverse chiamate su IEnumerable e IEnumerator: IEnumerable.GetEnumerator, chiamate a IEnumerator.MoveNext ripetute.

Prima chiamata GetEnumerator può essere implementato con esecuzione differita (modo più comune come query LINQ sono scritti) o esecuzione immediata (che sembra essere il caso della vostra collezione.

chiamate ai MoveNext potrebbero anche innescare l'esecuzione immediata di tutta la query anche se stai chiedendo solo per un singolo articolo o ogni chiamata può ottenere solo un singolo elemento.Perché la maggior parte delle query LINQ ottiene solo un elemento successivo dall'iteratore.

+0

Grazie per le informazioni approfondite. Sembra che tu abbia ragione. Se uso manualmente MoveNext(), questo è dove tutto viene bloccato. Sembra che il database non supporti l'acquisizione di un singolo elemento: un vero peccato. Proverò un altro database e segnalerò nel mio post originale. – BlackWolf

-1

Hai provato un ciclo più tradizionale per vedere se avrebbe funzionato?

IEnumerable<Result> resultSet = baseXClient.Query("...", "database"); 
for (int x =0; x < resultSet.Count; x++) 
{ 
    Result result = resultSet[x]; 
    ... 
} 
+1

L'esecuzione diversa è ciò che sta succedendo qui; questo non cambierà questo. Oltre a ciò, questo non verrà compilato come un 'IEnumerable' non ha una proprietà' Count' o un indicizzatore. – Servy

+1

a destra. Conta (dopo aver usato ToList()) o MoveNext() sull'Enumeratore, entrambi bloccano l'esecuzione, quindi non sembra esserci alcun modo per aggirare il problema. – BlackWolf

5

Lasciami Quess - non sta caricando i dati quando youcreate il rsultSet, ma quando è primo accesso (esecuzione in ritardo), e il caricamento di un milione di voci si prende un sacco di tempo a loro deserializzare in memoria .

Benvenuti alle inefficienze dei database XML.

+1

Beh, è ​​stato detto che BaseX sarebbe stato abbastanza veloce, ma proverò a caricare i dati in un database Apache Lucene oggi e vedere se ciò accelera le cose. Hai ragione di un milione è un sacco, e non mi aspetto che vengano restituiti in pochi secondi, ma non mi aspetto che anche loro impieghino più di 10 minuti. – BlackWolf

+0

Odio dirtelo, ma questo è un requisito molto duro. Sul serio. 1ms per articolo esce a 1000 secondi, che è 10 secondi. 1 ms non è tanto per "xml", che è notoriamente inefficiente da gestire per iniziare, quindi a seconda delle dimensioni dell'XML potrebbe richiedere più tempo. – TomTom

0

Per forzare la valutazione della query prima del foreach, chiamare la funzione ToList sul resultSet. (non risolverà il tuo problema se il problema è il database che dura per sempre)

+1

Chiamare 'ToList',' ToArray' o 'ToDictionary' ha lo stesso effetto. –

+0

La chiamata di 'Count' lo valuterà, ma se in seguito lo si" predispone "allora si otterrà un nuovo' IEnumerator' che deve essere valutato, il che significa che ora si sta facendo la query due volte. Se hai solo 'ToList' /' ToArray'/etc i dati e poi 'foreach', recupererai impazientemente i dati e li recupererai solo una volta. – Servy

2

Le risposte qui indicano tutte la causa del tuo problema percepito con foreach (esecuzione differita), ma non a una possibile soluzione. Non sono sicuro che questo database lo supporti, ma una soluzione potrebbe essere quella di provare il cercapersone ults in batch più piccoli, piuttosto che ottenere l'intera porzione di dati contemporaneamente.

Un'alternativa è scrivere una query di database che esegue i calcoli necessari, in modo che il database non debba inviare mai 1 milione di record ovunque. (Ancora una volta, non sono sicuro che questo database supporti questo)

+1

Grazie. Hai ragione, ci sono probabilmente soluzioni più sofisticate per quello che sto facendo qui. Tuttavia, sono sorpreso che il database impieghi A lungo per recuperare gli oggetti. Sfortunatamente, posso ottenere i dati in batch ma poiché devo eseguire una query UPDATE per ogni set di dati, che sfortunatamente non può essere parallelizzato, ciò non aiuterà molto. – BlackWolf

Problemi correlati