2009-04-30 3 views
6

Sto utilizzando C# per leggere un file CSV in formato semplice da 120 MB. Inizialmente ho fatto il parsing leggendolo riga per riga, ma di recente ho deciso che leggere prima l'intero contenuto del file in memoria era più volte più veloce. L'analisi è già abbastanza lenta perché il CSV contiene virgole racchiuse tra virgolette, il che significa che devo usare una divisione regolare. Questo è l'unico che ho trovato che funziona in modo affidabile:. NET System.OutOfMemoryException su String.Split() di file CSV da 120 MB

string[] fields = Regex.Split(line, 
@",(?!(?<=(?:^|,)\s*\x22(?:[^\x22]|\x22\x22|\\\x22)*,) 
(?:[^\x22]|\x22\x22|\\\x22)*\x22\s*(?:,|$))"); 
// from http://regexlib.com/REDetails.aspx?regexp_id=621 

Per fare il parsing dopo aver letto l'intero contenuto nella memoria, faccio una spaccatura corda sul carattere di nuova riga per ottenere un array che contiene ogni riga. Tuttavia, quando lo faccio sul file di 120 MB, ottengo un System.OutOfMemoryException. Perché esaurisce la memoria così rapidamente quando il mio computer ha 4 GB di RAM? C'è un modo migliore per analizzare rapidamente un CSV complicato?

risposta

7

È possibile ottenere un OutOfMemoryException praticamente per qualsiasi dimensione di allocazione. Quando assegni un pezzo di memoria, stai davvero chiedendo un pezzo di memoria continua della dimensione richiesta. Se questo non può essere onorato vedrai una OutOfMemoryException.

È necessario tenere presente che, a meno che non si utilizzi Windows a 64 bit, la RAM da 4 GB viene suddivisa in 2 GB di spazio del kernel e 2 GB di spazio utente, pertanto l'applicazione .NET non può accedere a più di 2 GB per impostazione predefinita.

Quando si eseguono operazioni con le stringhe in .NET, si rischia di creare molte stringhe temporanee a causa del fatto che le stringhe .NET sono immutabili. Di conseguenza, potresti vedere un aumento dell'uso della memoria piuttosto drammatico.

+0

stringhe sono il figlio bastardo dell'informatica. un male necessario, ma vorrei ancora che qualcuno potesse capire un modo migliore! –

4

Potresti non essere in grado di allocare un singolo oggetto con quella memoria molto contigua, né dovresti aspettarti di poterlo fare. Lo streaming è il modo normale per farlo, ma hai ragione che potrebbe essere più lento (anche se non credo che dovrebbe essere più lento).

Come compromesso, potresti provare a leggere un messaggio più grande porzione del file (ma ancora non tutto) contemporaneamente, con una funzione come StreamReader.ReadBlock() e l'elaborazione di ogni porzione a sua volta.

0

Probabilmente dovresti provare lo CLR profiler per determinare l'effettivo utilizzo della memoria. Potrebbe essere che ci siano limiti di memoria diversi dalla RAM del sistema. Ad esempio, se si tratta di un'applicazione IIS, la memoria è limitata dai pool di applicazioni.

Con queste informazioni sul profilo è possibile che sia necessario utilizzare una tecnica più scalabile come lo streaming del file CSV che si è tentato in origine.

5

Se l'intero file è stato letto in una stringa, è consigliabile utilizzare uno StringReader.

StringReader reader = new StringReader(fileContents); 
string line; 
while ((line = reader.ReadLine()) != null) { 
    // Process line 
} 

Questo dovrebbe essere analogo allo streaming da un file con la differenza che il contenuto è già presente nella memoria.

Modifica dopo aver testato

provato quanto sopra con un file di 140MB in cui il trattamento era costituito da incrementare lunghezza variabile con line.Length. Questo ha richiesto circa 1,6 secondi sul mio computer. Dopo questo ho provato il seguente:

System.IO.StreamReader reader = new StreamReader("D:\\test.txt"); 
long length = 0; 
string line; 
while ((line = reader.ReadLine()) != null) 
    length += line.Length; 

Il risultato è stato di circa 1 secondo.

Naturalmente il tuo chilometraggio può variare, specialmente se stai leggendo da un'unità di rete o l'elaborazione richiede abbastanza tempo per il disco rigido da cercare altrove. Ma anche se stai usando FileStream per leggere il file e non stai facendo il buffering. StreamReader fornisce il buffering che migliora notevolmente la lettura.

+0

Questa è una buona risposta se in realtà riesce a leggere il file in una stringa, il che suona come può, almeno al momento. Non sarei sorpreso se molte macchine fallissero immediatamente cercando di caricare un file da 120MB (o qualche volta non funzionassero e lavorassero altre volte.) – mquander

8

Non eseguire il parser a meno che non sia necessario. Ho avuto fortuna con questo:

A Fast CSV Reader

Se non altro si può guardare sotto il cofano e vedere come qualcun altro lo fa.

+1

+1 come ho usato per analizzare anche file CSV di grandi dimensioni. – Wayne

+1

+1 anche da parte mia. Nella mia esperienza, il lettore CSV di Sébastien Lorion è efficiente, flessibile e robusto. Dovrebbe masticare un file di 120 MB in pochissimo tempo. – LukeH

0

Si sta esaurendo la memoria nello stack, non l'heap.

Si potrebbe provare a rielaborare l'app in modo tale da elaborare l'input in "blocchi" di dati più gestibili anziché elaborare 120 MB alla volta.

+0

Le stringhe sono allocate nell'heap, non nello stack. Solo i primitivi di int/byte/double/etc sono mai assegnati nello stack imr. –

+0

@non sicuro: sei corretto. tuttavia, ci sono una varietà di circostanze non ovvie in cui lo stack del programma può riempire. Dato che il sistema in questione ha un'ampia memoria fisica, presumo che questo sia probabilmente uno di quei casi. =) – Garrett

+0

Lo stack che riempie produce una StackOverflowException, non una OutOfMemoryException; quest'ultimo viene sempre utilizzato per indicare memoria insufficiente nell'Heap GC. –

1

Come altri utenti dicono, l'OutOfMemory è perché non riesce a trovare un blocco contiguo di memoria della dimensione richiesta.

Tuttavia, si dice che eseguire l'analisi riga per riga è stato molte volte più veloce di leggere tutto in una volta e quindi eseguire l'elaborazione. Questo ha senso solo se si stesse perseguendo l'approccio ingenuo di fare il blocco si legge, ad esempio (in pseudo codice):

while(! file.eof()) 
{ 
    string line = file.ReadLine(); 
    ProcessLine(line); 
} 

Si dovrebbe invece usare lo streaming, in cui il flusso viene compilato dal Write() chiama da un supplente thread che sta leggendo il file, quindi il file letto non è bloccato da quello che fa ProcessLine() e viceversa. Questo dovrebbe essere in linea con le prestazioni di leggere l'intero file in una sola volta e quindi eseguire l'elaborazione.

+0

Puoi dare un esempio di codice dell'approccio multi-thread? Lo stavo facendo in modo ingenuo e ora capisco perché questo potrebbe essere un grosso problema. –

+0

.Net ha incorporato la lettura e scrittura di file asincroni, un buon punto di partenza è la chiamata BeginRead(). I seguenti risultati di Google hanno molti esempi: http://www.google.com/search?q=.net+asynchronous+file –

0

Sono d'accordo con la maggior parte di tutti qui, è necessario utilizzare lo streaming.

Non so se qualcuno ha detto finora, ma si dovrebbe guardare un metodo di estenzione.

e so, di sicuro, giù le mani, la migliore tecnica splitting CSV su .NET/CLR è this one

Questa tecnica mi ha generato + di 10GB output XML da un input CSV, tra cui filtri di ingresso exstensive e tutti, più veloce di qualsiasi altra cosa abbia visto.

+0

Oh, giusto, anche Streaming> Buffering nella tua RAM, non importa quale. Pensaci, se hai 4GIG e carichi 2GIG di input, solo il tempo di caricamento e il thrashing del tuo sottosistema VM per il reindirizzamento delle pagine e l'enorme dimensione della tua tabella di pagina andranno a finire nella cache della CPU, ecc. .. dentro/fuori da uno spazio di lavoro piccolo, facile da gestire, mantenere la cache "calda" e tutto il tempo della CPU è dedicato all'attività in corso, non all'enorme flusso del carico di sistema ... – RandomNickName42

0

Si dovrebbe leggere un blocco in un buffer e lavorare su quello. Poi leggi un altro pezzo e così via.

Ci sono molte librerie là fuori che lo faranno in modo efficiente per voi. Ne mantengo uno chiamato CsvHelper. Esistono molti casi limite che è necessario gestire, ad esempio quando una virgola o un finale di riga si trova nel mezzo di un campo.