optimización SED (modificación de archivos de gran tamaño basado en pequeño conjunto de datos)

StackOverflow https://stackoverflow.com/questions/848914

Pregunta

Lo que tengo que hacer frente a grandes archivos de texto plano (más de 10 gigabytes, sí sé que depende de lo que podríamos llamar grande), con líneas muy largas.

Mi tarea más reciente involucra algo de edición de línea base a datos de otro archivo.

El archivo de datos (que debe ser modificado) contiene 1500000 líneas, cada una de ellos son, por ejemplo, 800 caracteres de largo. Cada línea es único, y contiene sólo un número de identidad, cada número de identidad es única)

El archivo modificador es, por ejemplo, 1800 líneas de largo, contiene un número de identidad, y una cantidad y una fecha que debe ser modificado en el archivo de datos.

Yo sólo transformadas (Vim con expresiones regulares) el archivo modificador de sed, pero es muy ineficiente.

Vamos a decir que tengo una línea como la siguiente en el archivo de datos:

(some 500 character)id_number(some 300 character)

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

Con base en el archivo modificador, vengo con líneas de sed como este:

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

Así que tengo 1800 líneas como esta.

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 manera mejor?

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

¿Fue útil?

Solución

Mis enfoques sugeridos (en orden de manera deseable) serían para procesar estos datos como:

  1. Una base de datos (incluso una simple base de datos basada en SQLite con un índice llevará a cabo mucho mejor que la sed / awk en un archivo de 10 GB)
  2. Un archivo plano que contiene las longitudes de registro fijos
  3. Un archivo plano que contiene las longitudes de registro de variables

El uso de una base de datos se encarga de todos esos pequeños detalles que ralentizan el procesamiento de archivos de texto (encontrar el registro que interesa, la modificación de los datos, el almacenamiento de nuevo a la base de datos). Echar un vistazo por DBD :: SQLite en el caso de Perl.

Si desea seguir con archivos planos, tendrá que mantener un índice manualmente junto con el archivo grande, así que puede mirar más fácilmente los números de registros que necesita para manipular. O, mejor aún, tal vez sus números de identificación son sus números de registro?

Si usted tiene longitudes de registro de variables, me gustaría sugerir la conversión a longitudes fijas-Record (ya que sólo aparece su identificación es de longitud variable). Si no puede hacer eso, tal vez todos los datos existentes no siempre moverse en el archivo? A continuación, puede mantener ese índice se ha mencionado anteriormente y añadir nuevas entradas según sea necesario, con la diferencia es que en lugar del índice que apunta al registro número, que ahora apunta a la posición absoluta en el archivo.

Otros consejos

Te sugiero un Programm escrito en Perl (ya que no soy un gurú de la sed / awk y no hacen lo que se exactamente capaz de).

"algoritmo" es simple: lo que necesita para construir, en primer lugar, un mapa hash que se podría dar a la nueva cadena de datos de la aplicación para cada ID. Esto se consigue leer el archivo modificador del curso.

Una vez que esto hasmap en poblado puede navegar por cada línea del archivo de datos, lea la ID en el centro de la línea, y generar la nueva línea como se ha descrito anteriormente.

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

Con el Perl que debe utilizar substr para obtener id_number, especialmente si tiene id_number anchura constante.

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

Después de que si $ id_number está dentro del rango, se debe utilizar substr para sustituir el texto restante.

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

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

Mi sugerencia es, no utilice la base de datos. script de perl bien escrito superará a la base de datos en el orden de magnitud de este tipo de tarea. Confía en mí, tengo muchas experiencia práctica con él. Usted no va a haber importado los datos en la base de datos cuando se terminarán Perl.

Cuando se escribe 1500000 líneas con 800 caracteres parece 1,2 GB para mí. Si va a tener muy lento disco (30MB / s) se lee en unos 40 segundos. Con mejores 50 -> 24s, 100 -> 12 años y menos. Pero Perl de búsqueda de hash (como db unirse a) la velocidad de 2 GHz CPU está por encima de 5Mlookups / s. Esto significa que la CPU de trabajo con destino será en cuestión de segundos y IO trabajo con destino estará en decenas de segundos. Si es realmente números de 10GB se cambian, pero la proporción es la misma.

No ha especificado si la modificación de datos cambia de tamaño o no (si la modificación se puede hacer en su lugar) por lo tanto no vamos a asumir y funcionará como filtro. No ha especificado qué formato de su "expediente del 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 escribir 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 aproximadamente media hora durante 1,5 millones de filas, 1800 ids de búsqueda, los datos de 1,2 GB. Por 10GB no debería ser más de 5 minutos. Es rápida razonable para usted?

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

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

Usted debe casi seguro que usar una base de datos, como MikeyB sugirió .

Si no desea utilizar una base de datos por alguna razón, entonces, si la lista de modificaciones cabe en la memoria (ya que actualmente se a 1800 líneas), el método más eficaz es una tabla hash poblada con las modificaciones que sugiere 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 de combinación - básicamente:

  1. Compare la Identificación en la "cima" del archivo de entrada con el ID en la "cima" de las modificaciones de archivos
  2. Ajuste el registro en consecuencia si coinciden
  3. Escríbelo a cabo
  4. Descartar la línea "top" de cualquier archivo que tenía el (orden alfabético o numérico) más bajo de identificación y leer otra línea de ese archivo
  5. Goto 1.

Detrás de las escenas, una base de datos es casi seguro que usar una lista de fusionar si realiza esta alteración utilizando un SQL sola UPDATE comandos.

buena oferta en el sqlloader o DataDump decisión. Ese es el camino a seguir.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top