2009-12-01 24 views
9

que han heredado este fragmento de secuencia de comandos sed que los intentos de eliminar ciertos espacios vacíos:¿Cómo hacer que este script sed sea más rápido?

s/[\s\t]*|/|/g 
s/|[\s\t]*/|/g 
s/[\s] *$//g 
s/^|/null|/g 

que opera en un archivo que es de alrededor de 1 Gb grande. Esta secuencia de comandos se ejecuta durante 2 horas en nuestro servidor Unix. ¿Alguna idea de cómo acelerarlo?

Observa que se encuentra el \ s para un espacio y \ t representa una pestaña, el guión real utiliza el espacio real y la ficha y no los símbolos

El archivo de entrada es un archivo delimitado pipa y es ubicado localmente no en la red. Las 4 líneas están en un archivo ejecutado con sed -f

+0

¿Cómo estás invocando sed? ¿Está definitivamente el archivo en su disco local y no, por ejemplo, en un montaje NFS? –

+0

Archivo en el disco local. Invoco sed con sed -f – erotsppa

+1

Proporcione la línea de comando completa que está utilizando. Plain 'sed -f' lee de stdin y escribe en stdout, que obviamente no es lo que estás haciendo. –

Respuesta

25

La mejor pude ver con sed, era este script:

s/[\s\t]*|[\s\t]*/|/g 
s/[\s\t]*$// 
s/^|/null|/ 

En mis pruebas, esto se ejecutó aproximadamente un 30% más rápido que su script sed. El aumento en el rendimiento proviene de combinar los dos primeros regexen y omitir la bandera "g" donde no es necesario.

Sin embargo, un 30% más rápido es solo una mejora leve (todavía debería tomar aproximadamente una hora y media ejecutar el script anterior en su archivo de datos de 1GB). Quería ver si podía hacerlo mejor.

Al final, ningún otro método que probé (awk, perl y otros enfoques con sed) le fue mejor, excepto, por supuesto, una implementación simple de C. Como se esperaría con C, el código es un poco detallado para publicar aquí, pero si quiere un programa que probablemente sea más rápido que cualquier otro método, puede querer take a look at it.

En mis pruebas, la implementación C finaliza en aproximadamente el 20% del tiempo que le lleva a su script sed. Por lo tanto, podría llevar unos 25 minutos más o menos correr en su servidor Unix.

No pasé mucho tiempo optimizando la implementación de C. Sin duda, hay una serie de lugares donde el algoritmo podría mejorarse, pero francamente, no sé si es posible reducir una cantidad de tiempo significativa más allá de lo que ya se logra. En todo caso, creo que ciertamente coloca un límite superior en el tipo de rendimiento que puede esperar de otros métodos (sed, awk, perl, python, etc.).

Editar: La versión original tenía un error menor que causaba que imprimiera posiblemente lo incorrecto al final de la salida (por ejemplo, podría imprimir un "nulo" que no debería estar allí). Hoy tuve algo de tiempo para echarle un vistazo y arreglar eso. También optimicé una llamada a strlen() que le dio otro impulso de rendimiento leve.

+3

+1 por una cantidad impresionante de trabajo para implementar una solución más rápida y probarla adecuadamente en lugar de solo suponer que será más rápida. –

2

Me parece por su ejemplo que está limpiando el espacio en blanco de los campos delimitados por principio y fin de tubería (|) en un archivo de texto. Si tuviera que hacer esto, me gustaría cambiar el algoritmo a la siguiente:

for each line 
    split the line into an array of fields 
    remove the leading and trailing white space 
    join the fields back back together as a pipe delimited line handling the empty first field correctly. 

También me gustaría utilizar un lenguaje diferente, como Perl o Ruby para esto.

La ventaja de este enfoque es que el código que limpia las líneas ahora maneja menos caracteres para cada invocación y debe ejecutarse mucho más rápido, aunque se necesitan más invocaciones.

+2

+1. Con su algoritmo, 'awk' sería una mejor opción. – mouviciel

+0

¿Puede sugerir un comando awk para hacer esto? Lo siento, no soy un experto en awk – erotsppa

+0

Probé este algoritmo usando awk (en la misma línea que los programas awk sugeridos por D. Williamson y levislevis85), y fue un poco más lento que el script sed del OP, y bastante más lento que las versiones optimizadas del script sed Así que no estoy convencido de que este enfoque general (de dividir los registros en campos antes de la sustitución de patrones) pueda dar como resultado una aceleración (independientemente del idioma). –

2

trate de cambiar las dos primeras líneas de:

s/[ \t]*|[ \t]*/|/g 
+0

Mi prueba no ha encontrado diferencias entre esto y hacerlo por separado. –

+1

Mi prueba ha encontrado que esto disminuye el tiempo que se tarda en analizar un archivo de 250 MB. También encontré una disminución adicional al eliminar las opciones 'g' donde no es necesario. –

0

trata de hacerlo en un solo comando:

sed 's/[^|]*(|.*|).*/\1/' 
+0

No tiene ningún efecto. –

+0

¿Puede proporcionarnos algunos datos de prueba? Es difícil escribir una expresión regular correctamente sin pruebas :) –

0

Ha intentado Perl? Puede ser más rápido.

#!/usr/local/bin/perl -p 

s#[\t ]+\|#|#g; 
s#\|[\t ]+#|#g; 
s#[\t ]*$##; 
s#^\|#null|#; 

Editar: En realidad, parece que hay cerca de tres veces más lento que el programa de sed. Extraño ...

1

Este script Perl debe ser mucho más rápido

s/\s*|\s*/|/go; 
s/\s *$//o; 
s/^|/null|/o; 

Básicamente, asegúrese de que sus expresiones regulares se compilan una vez (la 'o' bandera), y sin necesidad de que tenga que utilizar 'g' en expresiones regulares que se aplican solo al final y al comienzo de la línea.

También, [\ s \ t] * es equivalente a \ s *

+0

La bandera "o" es redundante en ese contexto. Perl siempre compila una expresión regular una vez, a menos que tenga una variable dentro. –

1

Esto podría funcionar. Solo lo he probado un poco.

awk 'BEGIN {FS="|"; OFS="|"} {for (i=1; i<=NF; i++) gsub("[ \t]", "", $i); $1=$1; if ($1 == "") $1 = "null"; print}' 
+0

Las pruebas preliminares muestran que esto se ejecuta aproximadamente a la misma velocidad que las versiones 'sed'. –

1

¿Qué hay de Perl:

#!/usr/bin/perl 

while(<>) { 
    s/\s*\|\s*/|/g; 
    s/^\s*//; 
    s/\s*$//; 
    s/^\|/null|/; 
    print; 
} 

EDIT: enfoque cambió significativamente. En mi máquina, esto es casi 3 veces más rápido que tu script sed.

Si realmente necesita la mejor velocidad posible, escriba un programa especializado de C para realizar esta tarea.

+0

En mi máquina, esto es un poco más lento que el script sed del OP y toma el doble de tiempo que una versión más optimizada del script sed del OP. –

1

use gawk, not sed.

awk -vFS='|' '{for(i=1;i<=NF;i++) gsub(/ +|\t+/,"",$i)}1' OFS="|" file 
3

Mi prueba indicó que sed puede convertirse en CPU muy fácilmente en algo como esto. Si usted tiene una máquina multi-núcleo puede probar desove frente a múltiples procesos de sed con un guión que se ve algo como esto:

#!/bin/sh 
INFILE=data.txt 
OUTFILE=fixed.txt 
SEDSCRIPT=script.sed 
SPLITLIMIT=`wc -l $INFILE | awk '{print $1/20}'` 

split -d -l $SPLITLIMT $INFILE x_ 

for chunk in ls x_?? 
do 
    sed -f $SEDSCRIPT $chunk > $chunk.out & 
done 

wait 

cat x_??.out >> output.txt 

rm -f x_?? 
rm -f x_??.out 
+0

Uso inútil de 'ls'. Haga esto en su lugar: 'para el fragmento en x _ ??' y globbing está ordenado así que no hay necesidad de un bucle aquí: 'cat x _ ??. Out> output.txt' –

+0

Editado para incluir los comentarios de Dennis. – Drewfer

0

Creo que el * en las expresiones regulares en la pregunta y la mayoría de las respuestas puede ser una desaceleración importante en comparación con el uso de un +. Considere reemplazar a la primera pregunta en el

s/[\s\t]*|/|/g 

los partidos * cero o más elementos seguida de una |, por lo tanto, todos los | se sustituye incluso aquellos que no necesitan ser reemplazados. Cambio de la reemplace ser

s/[\s\t]+|/|/g 

sólo cambiará la | personajes que están precedidos por uno o más espacios y las pestañas.

No tengo sed disponible, pero hice un experimento con Perl. En los datos que utilicé el script con el * tomó casi 7 veces más tiempo que el script con +.

Los tiempos fueron consistentes en todas las carreras.Para el +, la diferencia entre los tiempos mínimo y máximo fue del 4% del promedio y para el * fue del 3,6%. La relación de los tiempos promedio fue de 1 :: 6.9 para + :: *.

detalles del experimento

probada usando un archivo de 80 MB con poco más de 180.000 casos de [st]\., estos son los caracteres en minúsculas y st.

La prueba utilizó un archivo de comando por lotes con 30 de cada uno de estos dos comandos, alternar la estrella y más.

perl -f TestPlus.pl input.ltrar > zz.oo 
perl -f TestStar.pl input.ltrar > zz.oo 

Un guión está por debajo, el otro simplemente cambió el *-+ y star-plus.

#! /bin/usr/perl 
use strict; 
use warnings; 
use Time::HiRes qw(gettimeofday tv_interval); 

my $t0 = [gettimeofday()]; 
while(<>) 
{ 
    s/[st]*\././g; 
} 

my $elapsed = tv_interval ($t0); 
print STDERR "Elapsed star $elapsed\n"; 

versión de Perl utilizado:

c:\test> perl -v 
This is perl 5, version 16, subversion 3 (v5.16.3) built for MSWin32-x64-multi-thread 
(with 1 registered patch, see perl -V for more detail) 

Copyright 1987-2012, Larry Wall 

Binary build 1603 [296746] provided by ActiveState http://www.ActiveState.com 
Built Mar 13 2013 13:31:10 
Cuestiones relacionadas