2009-04-30 23 views
6

Estoy usando C# para leer un archivo CSV de texto plano de ~ 120 MB. Inicialmente, hice el análisis leyendo línea por línea, pero recientemente determiné que la lectura de todo el contenido del archivo en la memoria era mucho más rápido. El análisis ya es bastante lento porque el CSV tiene comas incrustadas dentro de comillas, lo que significa que tengo que usar una división de expresiones regulares. Este es el único que he encontrado que funciona de forma fiable:.NET System.OutOfMemoryException en String.Split() de 120 MB de archivo CSV

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 

Con el fin de hacer el análisis después de leer todo el contenido en la memoria, hago una fracción de cadena en el carácter de nueva línea para obtener una matriz que contiene cada línea. Sin embargo, cuando hago esto en el archivo de 120 MB, obtengo un System.OutOfMemoryException. ¿Por qué se queda sin memoria tan rápido cuando mi computadora tiene 4 GB de RAM? ¿Hay una mejor manera de analizar rápidamente un CSV complicado?

Respuesta

7

Puede obtener una OutOfMemoryException básicamente para cualquier tamaño de asignación. Cuando asigna una pieza de memoria, realmente está pidiendo una pieza continua de memoria del tamaño solicitado. Si eso no se puede cumplir, verá una OutOfMemoryException. También debe tener en cuenta que a menos que esté ejecutando Windows de 64 bits, sus 4 GB de RAM se dividen en 2 GB de espacio en el kernel y 2 GB de espacio de usuario, por lo que su aplicación .NET no puede acceder a más de 2 GB de manera predeterminada.

Al realizar operaciones de cadena en .NET, corre el riesgo de crear muchas cadenas temporales debido a que las cadenas .NET son inmutables. Por lo tanto, es posible que el uso de la memoria aumente de forma espectacular.

+0

Las cuerdas son el hijo bastardo de la informática. un mal necesario, ¡pero todavía deseo que alguien descubra una mejor manera! –

4

Es posible que no pueda asignar un solo objeto con tanta memoria contigua, ni debe esperar poder hacerlo. La transmisión por secuencias es la forma más común de hacerlo, pero tiene razón en que podría ser más lento (aunque no creo que deba ser mucho más lento).

Como solución de compromiso, puede intentar leer una versión más grande parte del archivo (pero todavía no todo) a la vez, con una función como StreamReader.ReadBlock(), y procesar cada porción sucesivamente.

0

Probablemente deberías probar el CLR profiler para determinar el uso real de tu memoria. Puede ser que haya límites de memoria además de la RAM de su sistema. Por ejemplo, si se trata de una aplicación IIS, su memoria está limitada por los grupos de aplicaciones.

Con esta información de perfil puede encontrar que necesita utilizar una técnica más escalable como la transmisión del archivo CSV que intentó originalmente.

5

Si tiene todo el archivo leído en una cadena probablemente debería usar un StringReader.

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

Esto debería ser exactamente igual a la transmisión desde un archivo con la diferencia de que el contenido ya está en la memoria.

Editar después de probar

Probamos el anterior con un archivo de 140MB cuando el tratamiento consistió en incrementar longitud variable con line.Length. Esto tomó alrededor de 1.6 segundos en mi computadora. Después de esto intenté lo siguiente:

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

El resultado fue alrededor de 1 segundo.

Por supuesto, su kilometraje puede variar, especialmente si está leyendo desde una unidad de red o si su procesamiento lleva suficiente tiempo para que el disco duro busque otro. Pero también si está utilizando FileStream para leer el archivo y no está almacenando en el búfer. StreamReader proporciona almacenamiento en búfer que mejora en gran medida la lectura.

+0

Esta es una muy buena respuesta si realmente puede leer el archivo en una cadena en primer lugar, lo que parece que puede, al menos por el momento. No me sorprendería que muchas máquinas fallaran inmediatamente al intentar cargar un archivo de 120MB (o que fallara a veces y funcionó otras veces). – mquander

8

No mueva su propio analizador a menos que sea necesario. He tenido suerte con éste:

A Fast CSV Reader

Si nada más se puede mirar debajo del capó y ver cómo alguien más lo hace.

+1

+1 como lo he usado para analizar grandes archivos CSV también. – Wayne

+1

+1 de mí también. En mi experiencia, el lector CSV de Sébastien Lorion es eficiente, flexible y robusto. Debería masticar un archivo de 120 MB en poco tiempo. – LukeH

0

Se está quedando sin memoria en la pila, no en el montón.

Puede intentar volver a factorizar su aplicación de modo que esté procesando la entrada en "fragmentos" de datos más manejables en lugar de procesar 120 MB a la vez.

+0

Las cadenas se asignan en el montón, no en la pila. Solo las primitivas de int/byte/double/etc se asignan en la pila imr. –

+0

@no estoy seguro: estás en lo correcto. sin embargo, hay una variedad de circunstancias no obvias en las que la pila de programas puede llenarse. Dado que el sistema en cuestión tiene una memoria física amplia, supongo que este es probablemente uno de esos casos. =) – Garrett

+0

La pila que se llena da como resultado una StackOverflowException, no una OutOfMemoryException; este último siempre se usa para indicar memoria insuficiente en el Heap de GC. –

1

Como dicen otros carteles, OutOfMemory se debe a que no puede encontrar un trozo contiguo de memoria del tamaño solicitado.

Sin embargo, usted dice que hacer el análisis línea por línea fue varias veces más rápido que leerlo todo de una vez y luego procesarlo. Esto sólo tiene sentido si estuviera llevando a cabo el enfoque ingenuo de hacer el bloqueo lee, por ejemplo (en pseudocódigo):

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

su lugar, debe usar el streaming, donde el torrente se rellena por Write() llama a un suplente hilo que está leyendo el archivo, por lo que el archivo leído no está bloqueado por lo que haga su ProcessLine(), y viceversa. Eso debería estar a la par con la interpretación de leer el archivo completo a la vez y luego hacer su procesamiento.

+0

¿Podría dar un ejemplo de código del enfoque de subprocesos múltiples? Lo estaba haciendo de la manera ingenua, y ahora entiendo por qué podría ser un gran problema. –

+0

.Net tiene una función de lectura y escritura de archivos asíncrona, un buen punto de partida es la llamada BeginRead(). Los siguientes resultados de Google tienen muchos ejemplos: http://www.google.com/search?q=.net+asynchronous+file –

0

Estoy de acuerdo con la mayoría de los usuarios aquí, necesita usar la transmisión por secuencias.

No sé si alguien ha dicho hasta ahora, pero debería consultar un método de atención.

Y sé, a ciencia cierta, sin duda, la mejor técnica de división CSV en .NET/CLR es this one

Esa técnica me generó + de 10 GB salida XML desde la entrada CSV, incluyendo filtros de entrada exstensive y todo, más rápido que cualquier otra cosa que he visto.

+0

Oh, a la derecha, también, Streaming> Buffering en tu RAM pase lo que pase. Piénselo, si tiene 4GIG, y carga 2GIG de entrada, solo el tiempo de carga y la manipulación de sus páginas de reubicación del subsistema VM, y el tamaño masivo de su tabla de páginas simplemente consumirá su caché de CPU, etc. .. dentro/fuera de un espacio de trabajo pequeño y fácil de administrar, mantenga su caché "caliente" y todo el tiempo de su CPU se dedica a la tarea en cuestión, no a la masiva fluctuación en la carga del sistema ... – RandomNickName42

0

Debe leer un fragmento en un búfer y trabajar en eso. Luego lee otro trozo y así sucesivamente.

Hay muchas bibliotecas que lo harán de manera eficiente. Mantengo uno llamado CsvHelper. Hay una gran cantidad de casos extremos que debe manejar, como cuando una coma o línea termina en el medio de un campo.

Cuestiones relacionadas