Procesamiento de texto-Python vs Perl performance
regex text-processing (5)
Aquí está mi script de Perl y Python para hacer un procesamiento de texto simple de aproximadamente 21 archivos de registro, cada uno de aproximadamente 300 KB a 1 MB (máximo) multiplicado por 5 veces (un total de 125 archivos, debido al registro repetido 5 veces).
Código de Python (código modificado para usar re
compilado y usar re.I
)
#!/usr/bin/python
import re
import fileinput
exists_re = re.compile(r''^(.*?) INFO.*Such a record already exists'', re.I)
location_re = re.compile(r''^AwbLocation (.*?) insert into'', re.I)
for line in fileinput.input():
fn = fileinput.filename()
currline = line.rstrip()
mprev = exists_re.search(currline)
if(mprev):
xlogtime = mprev.group(1)
mcurr = location_re.search(currline)
if(mcurr):
print fn, xlogtime, mcurr.group(1)
Código Perl
#!/usr/bin/perl
while (<>) {
chomp;
if (m/^(.*?) INFO.*Such a record already exists/i) {
$xlogtime = $1;
}
if (m/^AwbLocation (.*?) insert into/i) {
print "$ARGV $xlogtime $1/n";
}
}
Y, en mi PC, ambos códigos generan exactamente el mismo archivo de resultados de 10,790 líneas. Y, aquí está el tiempo en las implementaciones de Cygwin Perl y Python.
User@UserHP /cygdrive/d/tmp/Clipboard
# time /tmp/scripts/python/afs/process_file.py *log* *log* *log* *log* *log* >
summarypy.log
real 0m8.185s
user 0m8.018s
sys 0m0.092s
User@UserHP /cygdrive/d/tmp/Clipboard
# time /tmp/scripts/python/afs/process_file.pl *log* *log* *log* *log* *log* >
summarypl.log
real 0m1.481s
user 0m1.294s
sys 0m0.124s
Originalmente, tomó 10.2 segundos usando Python y solo 1.9 segundos usando Perl para este simple procesamiento de texto.
(ACTUALIZACIÓN) pero, después de la versión revisada de Python, ahora demora 8.2 segundos en Python y 1.5 segundos en Perl. Still Perl es mucho más rápido.
¿Hay alguna manera de mejorar la velocidad de Python? O es obvio que Perl será el más rápido para el procesamiento de texto simple.
Por cierto, esta no fue la única prueba que hice para el procesamiento de texto simple ... Y, cada forma diferente que hago el código fuente, siempre siempre Perl gana por un amplio margen. Y, ni una sola vez Python se desempeñó mejor para simples m/regex/
match e imprimir cosas.
Por favor, no sugiera usar C, C ++, Assembly, otros sabores de Python, etc.
Estoy buscando una solución usando Standard Python con sus módulos integrados en comparación con Standard Perl (ni siquiera usando los módulos). Chico, deseo usar Python para todas mis tareas debido a su legibilidad, pero para perder velocidad, no lo creo.
Por lo tanto, sugiera cómo se puede mejorar el código para obtener resultados comparables con Perl.
ACTUALIZACIÓN: 2012-10-18
Como otros usuarios sugirieron, Perl tiene su lugar y Python tiene su.
Entonces, para esta pregunta, uno puede concluir con seguridad que para una simple coincidencia de expresiones regulares en cada línea para cientos o miles de archivos de texto y escribir los resultados en un archivo (o impresión en pantalla), Perl siempre, siempre GANARÁ en rendimiento para este trabajo . Es tan simple como eso.
Tenga en cuenta que cuando digo que Perl gana en rendimiento ... solo se compara el estándar Perl y Python ... no recurriendo a algunos módulos oscuros (desconocidos para un usuario normal como yo) y tampoco llamando a C, C ++, librerías de ensamblado de Python o Perl. No tenemos tiempo para aprender todos estos pasos e instalación adicionales para un trabajo simple de coincidencia de texto.
Por lo tanto, Perl oscila para el procesamiento de textos y expresiones regulares.
Python tiene su lugar para rockear en otros lugares.
Actualización 2013-05-29: un excelente artículo que hace una comparación similar está aquí . Perl nuevamente gana por la coincidencia de texto simple ... Y para más detalles, lea el artículo.
En general, todos los puntos de referencia artificiales son malvados. Sin embargo, todo lo demás es igual (enfoque algorítmico), puede hacer mejoras en una base relativa. Sin embargo, debe tenerse en cuenta que no uso Perl, por lo que no puedo discutir a su favor. Dicho esto, con Python puede intentar usar Pyrex o Cython para mejorar el rendimiento. O, si eres aventurero, puedes intentar convertir el código Python en C ++ a través de ShedSkin (que funciona para la mayoría del lenguaje central, y algunos, pero no todos, de los módulos centrales).
Sin embargo, puede seguir algunos de los consejos publicados aquí:
Espero que Perl sea más rápido. Siendo curioso, ¿puedes probar lo siguiente?
#!/usr/bin/python
import re
import glob
import sys
import os
exists_re = re.compile(r''^(.*?) INFO.*Such a record already exists'', re.I)
location_re = re.compile(r''^AwbLocation (.*?) insert into'', re.I)
for mask in sys.argv[1:]:
for fname in glob.glob(mask):
if os.path.isfile(fname):
f = open(fname)
for line in f:
mex = exists_re.search(line)
if mex:
xlogtime = mex.group(1)
mloc = location_re.search(line)
if mloc:
print fname, xlogtime, mloc.group(1)
f.close()
Actualización como reacción a "es demasiado complejo" .
Por supuesto, parece más complejo que la versión de Perl. El Perl fue construido alrededor de las expresiones regulares. De esta manera, difícilmente puede encontrar un lenguaje interpretado que sea más rápido en expresiones regulares. La sintaxis de Perl ...
while (<>) {
...
}
... también esconde muchas cosas que deben hacerse de alguna manera en un lenguaje más general. Por otro lado, es bastante fácil hacer que el código Python sea más legible si mueve la parte ilegible:
#!/usr/bin/python
import re
import glob
import sys
import os
def input_files():
''''''The generator loops through the files defined by masks from cmd.''''''
for mask in sys.argv[1:]:
for fname in glob.glob(mask):
if os.path.isfile(fname):
yield fname
exists_re = re.compile(r''^(.*?) INFO.*Such a record already exists'', re.I)
location_re = re.compile(r''^AwbLocation (.*?) insert into'', re.I)
for fname in input_files():
with open(fname) as f: # Now the f.close() is done automatically
for line in f:
mex = exists_re.search(line)
if mex:
xlogtime = mex.group(1)
mloc = location_re.search(line)
if mloc:
print fname, xlogtime, mloc.group(1)
Aquí el def input_files()
se puede colocar en otro lugar (por ejemplo, en otro módulo) o puede reutilizarse. Es posible imitar incluso el tiempo de Perl while (<>) {...}
fácilmente, aunque no de la misma manera sintáctica:
#!/usr/bin/python
import re
import glob
import sys
import os
def input_lines():
''''''The generator loops through the lines of the files defined by masks from cmd.''''''
for mask in sys.argv[1:]:
for fname in glob.glob(mask):
if os.path.isfile(fname):
with open(fname) as f: # now the f.close() is done automatically
for line in f:
yield fname, line
exists_re = re.compile(r''^(.*?) INFO.*Such a record already exists'', re.I)
location_re = re.compile(r''^AwbLocation (.*?) insert into'', re.I)
for fname, line in input_lines():
mex = exists_re.search(line)
if mex:
xlogtime = mex.group(1)
mloc = location_re.search(line)
if mloc:
print fname, xlogtime, mloc.group(1)
Entonces, el último for
puede parecer tan fácil (en principio) como el de Perl while (<>) {...}
. Tales mejoras de legibilidad son más difíciles en Perl.
De todos modos, no hará que el programa Python sea más rápido. Perl volverá a ser más rápido aquí. Perl es un cruncher de archivos / texto. Pero, en mi opinión, Python es un mejor lenguaje de programación para fines más generales.
Este es exactamente el tipo de cosas para las que Perl fue diseñado, por lo que no me sorprende que sea más rápido.
Una optimización fácil en su código de Python sería precompilar esas expresiones regulares, por lo que no se vuelven a compilar cada vez.
exists_re = re.compile(r''^(.*?) INFO.*Such a record already exists'')
location_re = re.compile(r''^AwbLocation (.*?) insert into'')
Y luego en tu ciclo:
mprev = exists_re.search(currline)
y
mcurr = location_re.search(currline)
Eso por sí solo no traerá mágicamente su secuencia de comandos de Python en línea con su secuencia de comandos Perl, pero llamar repetidamente a re en un bucle sin compilar primero es una mala práctica en Python.
Hipótesis: Perl pasa menos tiempo retrocediendo en líneas que no coinciden debido a las optimizaciones que tiene Python.
¿Qué obtienes reemplazando
^(.*?) INFO.*Such a record already exists
con
^((?:(?! INFO).)*?) INFO.*Such a record already
o
^(?>(.*?) INFO).*Such a record already exists
Las llamadas a funciones son un poco costosas en términos de tiempo en Python. Y sin embargo, tiene una llamada de función invariante de bucle para obtener el nombre del archivo dentro del bucle:
fn = fileinput.filename()
Mueva esta línea por encima del ciclo for
y verá una mejora en el tiempo de Python. Sin embargo, probablemente no sea suficiente para vencer a Perl.