2011-01-30 14 views
6

Estoy escribiendo un script de importación que procesa un archivo que tiene potencialmente cientos de miles de líneas (archivo de registro). El uso de un enfoque muy simple (a continuación) llevó suficiente tiempo y memoria como para pensar que eliminaría mi MBP en cualquier momento, así que maté el proceso.Cómo analizar eficazmente archivos de texto grandes en Ruby

#... 
File.open(file, 'r') do |f| 
    f.each_line do |line| 
    # do stuff here to line 
    end 
end 

Este archivo en particular, tiene 642,868 líneas:

$ wc -l nginx.log                                  /code/src/myimport 
    642868 ../nginx.log 

¿Alguien sabe de una manera más eficiente (memoria/CPU) para procesar cada línea de este archivo?

ACTUALIZACIÓN

El código dentro de la f.each_line desde arriba es simplemente que coincidan con una expresión regular contra la línea. Si la coincidencia falla, agrego la línea a una matriz @skipped. Si pasa, formateo las coincidencias en un hash (codificado por los "campos" de la coincidencia) y lo adjunto a una matriz @results.

# regex built in `def initialize` (not on each line iteration) 
@regex = /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) - (.{0})- \[([^\]]+?)\] "(GET|POST|PUT|DELETE) ([^\s]+?) (HTTP\/1\.1)" (\d+) (\d+) "-" "(.*)"/ 

#... loop lines 
match = line.match(@regex) 
if match.nil? 
    @skipped << line 
else 
    @results << convert_to_hash(match) 
end 

Estoy completamente abierto a que esto sea un proceso ineficiente. Podría hacer que el código dentro de convert_to_hash use un lambda precalculado en lugar de calcular el cálculo cada vez. Supongo que simplemente asumí que era la iteración de línea en sí el problema, no el código por línea.

+0

La forma más eficiente de la memoria es cómo lo está haciendo con 'each_line'. Podrías leer el archivo en bloques que es más rápido, luego usar 'String # lines' para tomar líneas individuales junto con volver a unir cualquier línea parcialmente cargada que cruzara los límites del bloque. Se convierte en un lavado tener que dividir las líneas y reunirse con las rotas. –

Respuesta

5

Acabo de hacer una prueba en un archivo de línea de 600,000 e iteraba sobre el archivo en menos de medio segundo. Supongo que la lentitud no está en el bucle del archivo sino en el análisis de la línea. ¿Puedes pegar tu código parse también?

+0

La única pieza de código que tiene algún significado es que estoy haciendo coincidir la línea con una expresión regular semi complicada. La expresión regular no hace ningún aspecto hacia adelante/atrás, es principalmente una coincidencia de char por char. Publicaré una actualización arriba con el código correspondiente. – localshred

+0

Ah, y la expresión regular se calcula una vez, no en cada iteración (solo para ser claro). – localshred

+0

Parece que fue mi tontería lo que estaba causando el crecimiento de la memoria. Estaba almacenando los resultados coincidentes (y también las líneas omitidas) en las matrices que estaba usando para hacer las inserciones de db más tarde (o para imprimir el tamaño de las omisiones). Lo sé, soy tonto.:) Ahora solo estoy haciendo un 'puts' en las líneas salteadas y haciendo el insert db justo cuando la coincidencia es válida. La memoria real nunca supera los 30mb. Gracias por señalar que probablemente estaba haciendo las cosas de una manera tonta. :) (Oh y yo cambiamos a 'IO.foreach' como sugerimos su respuesta original). – localshred

1

Si se está utilizando bash (o similar) que podría ser capaz de optimizar de esta manera:

En input.rb:

while x = gets 
     # Parse 
end 

entonces en bash:

cat nginx.log | ruby -n input.rb 

El La bandera -n le dice a ruby ​​assume 'while gets(); ... end' loop around your script, lo que podría hacer que haga algo especial para optimizar.

Es posible que también desee buscar una solución preescrita para el problema, ya que será más rápido.

+0

Parece un poco más hacky de lo que me gustaría en este momento, pero lo tendré en cuenta. – localshred

4

Este blogpost incluye varios métodos para analizar grandes archivos de registro. Tal vez sea una inspiración. También eche un vistazo a file-tail gem

Cuestiones relacionadas