2009-05-11 9 views
8

Tengo que ocuparme de archivos de texto sin formato muy grandes (más de 10 gigabytes, sí sé que depende de lo que deberíamos llamar grande), con líneas muy largas.optimización sed (modificación de archivos grandes basada en un conjunto de datos más pequeño)

Mi tarea más reciente implica cierta edición de línea basada en datos de otro archivo.

El archivo de datos (que debe modificarse) contiene 1500000 líneas, cada una de ellas es p. Ej. 800 caracteres de largo. Cada línea es única y contiene solo un número de identidad, cada número de identidad es único)

El archivo modificador es, p. 1800 líneas de longitud, contiene un número de identidad, una cantidad y una fecha que deben modificarse en el archivo de datos.

Acabo de transformar (con Vim Regex) el archivo modificador a sed, pero es muy ineficiente.

Digamos que tengo una línea como esta en el archivo de datos:

(some 500 character)id_number(some 300 character) 

Y tengo que modificar los datos en la parte 300 Char.

Basado en el archivo modificador, que llegar a las líneas de sed como este:

/id_number/ s/^\(.\{650\}\).\{20\}/\1CHANGED_AMOUNT_AND_DATA/ 

Así que tienen 1800 líneas de este tipo.

Pero sé, que incluso en un servidor muy rápido, si hago un

sed -i.bak -f modifier.sed data.file 

Es muy lento, ya que tiene que leer cada patrón x cada línea.

¿No hay una forma mejor?

Nota: No soy un programador, nunca había aprendido (en la escuela) sobre algoritmos. Puedo usar awk, sed, una versión desactualizada de perl en el servidor.

+1

Cuál es la versión de Perl? –

+0

perl 5.8.6 i586-linux-thread-multi –

+1

Esa * es * una versión obsoleta perl, pero sospecho que no es tan mala como la gente habría concluido de su nota;) – user55400

Respuesta

6

Mi sugirió enfoques (en orden de manera deseable) sería para procesar estos datos como:

  1. Una base de datos (incluso una sencilla basada en SQLite DB con un índice llevará a cabo mucho mejor que sed/awk en un archivo de 10 GB)
  2. un archivo plano que contiene de registro fija longitudes
  3. un archivo plano que contiene registro variable de longitudes de

Usando una base de datos toma c son todos esos pequeños detalles que ralentizan el procesamiento de archivos de texto (encontrar el registro que te interesa, modificar los datos, almacenarlo de nuevo en el DB). Eche un vistazo a DBD :: SQLite en el caso de Perl.

Si desea mantener archivos planos, querrá mantener un índice manualmente junto con el archivo grande para que pueda buscar más fácilmente los números de registro que necesitará manipular. O, mejor aún, tal vez sus números de identificación son sus números de registro?

Si tiene longitudes variables de registro, le sugiero convertir a longitudes de registro fijo (ya que parece que solo su ID es de longitud variable). Si no puede hacer eso, ¿quizás alguna información existente no se moverá en el archivo? Luego puede mantener ese índice mencionado anteriormente y agregar nuevas entradas según sea necesario, con la diferencia de que en lugar del índice que apunta al número de registro, ahora señala la posición absoluta en el archivo.

+0

Voy con el método DB. Oracle está disponible. Actualmente sqlldr-ing ... –

+1

La solución DB (con sqlldr, sqlplus) acaba de terminar, mientras que el sed sigue funcionando al 7% ... –

3

Te sugiero un programa escrito en Perl (ya que no soy un gurú sed/awk y no sé de lo que son exactamente capaces).

Tu "algoritmo" es simple: necesitas construir, antes que nada, un hashmap que podría darte la nueva cadena de datos para aplicar a cada ID. Esto se logra leyendo el archivo modificador, por supuesto.

Una vez que se haya rellenado este hasmap, puede navegar por cada línea de su archivo de datos, leer la ID en el medio de la línea y generar la nueva línea como se describió anteriormente.

No soy un gurú de Perl, pero creo que el programa es bastante simple.Si necesita ayuda para escribir, solicite que :-)

+0

Suena como una buena solución, siempre que la ID de una línea se puede extraer con un esfuerzo razonable, lo cual no está claro a partir de la pregunta, sino una buena suposición, yo. – user55400

2

Con perl debe usar substr para obtener id_number, especialmente si id_number tiene ancho constante.

my $id_number=substr($str, 500, id_number_length); 

Después de eso, si $ id_number está dentro del rango, debe usar substr para reemplazar el texto restante.

substr($str, -300,300, $new_text); 

Las expresiones regulares de Perl son muy rápidas, pero no en este caso.

0

Es casi seguro que utilice una base de datos, como MikeyB suggested.

Si no desea utilizar una base de datos por alguna razón, entonces si la lista de modificaciones cabe en la memoria (como lo hará en 1800 líneas), el método más eficiente es una tabla hash llena con las modificaciones como sugerido por yves Baumes.

Si se llega a tal punto que incluso la lista de modificaciones se pone muy grande, lo que necesita para ordenar los archivos por tanto sus documentos de identidad y luego realizar una lista fusionar - básicamente:

  1. Compare la identificación en el "top" del archivo de entrada con el ID en la "cima" de las modificaciones de archivos
  2. modificará el registro en consecuencia si coinciden
  3. escribirlo
  4. Descartar la línea "top" de cualquier archivo que tenía el (alfabéticamente) o numéricamente) ID más bajo y lea otra línea de ese archivo
  5. Ir a 1.

Detrás de escena, una base de datos casi seguramente utilizará una combinación de listas si realiza esta alteración con un solo comando SQL UPDATE.

0

Buena oferta en la decisión sqlloader o datadump. Ese es el camino a seguir.

+0

Esto debería haberse publicado como comentario. – Viet

1

Mi sugerencia es, no utilice la base de datos. El script de Perl bien escrito superará a la base de datos en orden de magnitud en este tipo de tarea. Confía en mí, tengo mucha experiencia práctica con eso. No habrá importado datos en la base de datos cuando se termine Perl.

Cuando escribe 1500000 líneas con 800 caracteres parece 1.2GB para mí. Si va a tener un disco muy lento (30MB/s), lo leerá en 40 segundos. Con mejores 50 -> 24s, 100 -> 12s y más. Pero la velocidad de búsqueda de hash perl (como db join) en la CPU de 2 GHz es superior a 5Mlookups/s. Significa que su trabajo vinculado a la CPU estará en segundos y su trabajo de IO bound será en decenas de segundos. Si realmente es 10GB, los números cambiarán pero la proporción es la misma.

No ha especificado si la modificación de los datos cambia de tamaño o no (si la modificación se puede hacer en el lugar), por lo tanto no lo asumiremos y funcionará como filtro. No ha especificado qué formato de su "archivo modificador" y qué tipo de modificación. Supongamos que está separada por pestaña algo como:

<id><tab><position_after_id><tab><amount><tab><data> 

vamos a leer los datos de la entrada estándar y escribe en la salida estándar y el guión puede ser algo como esto:

my $modifier_filename = 'modifier_file.txt'; 

open my $mf, '<', $modifier_filename or die "Can't open '$modifier_filename': $!"; 
my %modifications; 
while (<$mf>) { 
    chomp; 
    my ($id, $position, $amount, $data) = split /\t/; 
    $modifications{$id} = [$position, $amount, $data]; 
} 
close $mf; 

# make matching regexp (use quotemeta to prevent regexp meaningful characters) 
my $id_regexp = join '|', map quotemeta, keys %modifications; 
$id_regexp = qr/($id_regexp)/;  # compile regexp 

while (<>) { 
    next unless m/$id_regexp/; 
    next unless $modifications{$1}; 
    my ($position, $amount, $data) = @{$modifications{$1}}; 
    substr $_, $+[1] + $position, $amount, $data; 
} 
continue { print } 

En la mía portátil se tarda una media hora para 1,5 millones de filas, 1800 id de búsqueda, datos de 1,2 GB. Por 10 GB, no debería durar más de 5 minutos. ¿Es razonablemente rápido para ti?

Si empieza a pensar que no está obligado IO (por ejemplo, si utilizar algunos NAS), pero por CPU puede sacrificar algo de lectura y cambiar a esto:

my $mod; 
while (<>) { 
    next unless m/$id_regexp/; 
    $mod = $modifications{$1}; 
    next unless $mod; 
    substr $_, $+[1] + $mod->[0], $mod->[1], $mod->[2]; 
} 
continue { print } 
+0

Aunque terminé mi tarea, volveré a intentar su solución, ya que Oracle no siempre está disponible. De todos modos, gracias por tu ayuda. –

Cuestiones relacionadas