2012-04-20 13 views
23

Noté algunas demoras extremas en mis scripts de Ruby (1.9) y después de algunas búsquedas se redujeron a la coincidencia de expresiones regulares. Estoy usando los siguientes scripts de prueba en Perl y en Ruby:Expresión regular - Ruby vs Perl

Perl:

$fname = shift(@ARGV); 
open(FILE, "<$fname"); 
while (<FILE>) { 
    if (/(.*?) \|.*?SENDING REQUEST.*?TID=(.*?),/) { 
     print "$1: $2\n"; 
    } 
} 

Ruby:

f = File.open(ARGV.shift) 
while (line = f.gets) 
    if /(.*?) \|.*?SENDING REQUEST.*?TID=(.*?),/.match(line) 
     puts "#{$1}: #{$2}" 
    end 
end 

que utilizan la misma entrada para ambas secuencias de comandos, una archivo con solo 44290 líneas. El momento para cada uno es:

Perl:

[email protected]:~/bin/local/project$ time ./try.pl input >/dev/null 

real 0m0.049s 
user 0m0.040s 
sys  0m0.000s 

Ruby:

[email protected]:~/bin/local/project$ time ./try.rb input >/dev/null 

real 1m5.106s 
user 1m4.910s 
sys  0m0.010s 

supongo que estoy haciendo algo terriblemente estúpida, alguna sugerencia?

Gracias

+2

¿Has probado si la línea '= ~ /(.*) \ |?.?.?. * * Envío de una orden TID = (*), /'?? Eso también funciona en Ruby, me gustaría saber si tiene características de rendimiento diferentes. –

Respuesta

7
regex = Regexp.new(/(.*?) \|.*?SENDING REQUEST.*?TID=(.*?),/) 

f = File.open(ARGV.shift).each do |line| 
    if regex .match(line) 
     puts "#{$1}: #{$2}" 
    end 
end 

O

regex = Regexp.new(/(.*?) \|.*?SENDING REQUEST.*?TID=(.*?),/) 

f = File.open(ARGV.shift) 
f.each_line do |line| 
    if regex.match(line) 
    puts "#{$1}: #{$2}" 
    end 
+0

+1 Descubrí que Perl está haciendo esto automáticamente. – stema

+3

He intentado su sugerencia, pero no hubo ningún cambio, el tiempo de ejecución todavía es 1m5.134s – xpapad

+2

Algunos nitpicks: necesita liberar el descriptor de archivo después de que haya terminado con él, ya sea llamando 'close' o mediante' File .open ('nombre de archivo') {archivo | } ', que asegura que el archivo está cerrado. Además, '/#{...}/' denota un literal 'Regexp'; la llamada 'Regexp.new' es innecesaria. –

5

Desde la sección perlretut chapter: Using regular expressions in Perl - "Buscar y reemplazar"

(A pesar de que la expresión regular aparece en un bucle, Perl es lo suficientemente inteligente como para compilarlo una sola vez).

No sé Ruby muy bien, pero sospecho que compila la expresión regular en cada ciclo.
(Pruebe el código de la respuesta de LaGrandMere para verificarlo).

+0

. Lo dudo. Hay una sintaxis especial para él, por lo que probablemente esté construido durante la fase de análisis ... que está muy por delante del ciclo. – remram

5

Una posible diferencia es la cantidad de retroceso que se realiza. Es posible que Perl haga un mejor trabajo al eliminar el árbol de búsqueda cuando retrocede (es decir, cuando una parte de un patrón no puede coincidir). Su motor regex está altamente optimizado.

En primer lugar, la adición de un « ^ » podría hacer una gran diferencia. Si el patrón no coincide comenzando en la posición 0, ¡tampoco va a coincidir en la posición de inicio 1! Así que no intente hacer coincidir en la posición 1.

En la misma línea, « .*? » no es tan limitante como se podría pensar, y la sustitución de cada instancia de la misma con un patrón más limitante podría evitar una gran cantidad de retroceso .

Por qué no pruebas:

/ 
    ^
    (.*?)      [ ]\| 
    (?:(?!SENDING[ ]REQUEST).)* SENDING[ ]REQUEST 
    (?:(?!TID=).)*    TID= 
    ([^,]*)      , 
/x 

(No estoy seguro de si era seguro para reemplazar el primer « .*? » con « [^|] », así que no lo hizo.)

(al menos para los patrones que responden a una única cadena, (?:(?!PAT).) es PAT como [^CHAR] es CHAR.)

Usando /s podría acelerar las cosas si « . » se permite para que coincida con los saltos de línea, pero creo que es bastante menor.

Usando « \space » en lugar de « [space] » para que coincida con un espacio bajo /x podría ser un poco más rápido en Ruby. (Son lo mismo en las versiones recientes de Perl). Utilicé este último porque es mucho más legible.

+0

@xpapad, ajustó mi respuesta. – ikegami

1

Ruby:

File.open(ARGV.shift).each do |line| 
    if line =~ /(.*?) \|.*?SENDING REQUEST.*?TID=(.*?),/ 
     puts "#{$1}: #{$2}" 
    end 
end 

Cambio match método para =~ operador. Es más rápido debido a que:

(Ruby tiene Benchmark No sé el contenido de su archivo por lo que he escrito algo al azar.) Informe

require 'benchmark' 

def bm(n) 
    Benchmark.bm do |x| 
    x.report{n.times{"asdfajdfaklsdjfklajdklfj".match(/fa/)}} 
    x.report{n.times{"asdfajdfaklsdjfklajdklfj" =~ /fa/}} 
    x.report{n.times{/fa/.match("asdfajdfaklsdjfklajdklfj")}} 
    end 
end 

bm(100000) 

Salida:

 user  system  total  real 
    0.141000 0.000000 0.141000 ( 0.140564) 
    0.047000 0.000000 0.047000 ( 0.046855) 
    0.125000 0.000000 0.125000 ( 0.124945) 

El del medio es el uso =~. Toma menos de 1/3 de los demás. Otros dos están usando el método match. Por lo tanto, use =~ en su código.

+0

Intenté = ~ en lugar de coincidencia, sin cambios en el rendimiento. – xpapad

1

La coincidencia de expresiones regulares consume mucho tiempo en comparación con otras formas de emparejamiento. Como espera una cuerda larga y estática en el medio de las líneas correspondientes, intente filtrar las líneas que no incluyen esa cadena mediante operaciones de cuerda relativamente baratas. Eso debería dar como resultado menos que necesita pasar por el análisis de expresiones regulares (dependiendo de cómo se ve su entrada, por supuesto).

f = File.open(ARGV.shift) 
my_re = Regexp.new(/(.*?) \|.*?SENDING REQUEST.*?TID=(.*?),/) 
while (line = f.gets) 
    continue if line.index('SENDING REQUEST') == nil 
    if my_re.match(line) 
     puts "#{$1}: #{$2}" 
    end 
end 
f.close() 

No he evaluado esta versión en particular porque no tengo los datos de entrada. Sin embargo, he tenido éxito haciendo cosas como esta en el pasado, especialmente con archivos de registro largos en los que el prefiltrado puede eliminar la gran mayoría de las entradas sin ejecutar expresiones regulares.

2

Pruebe usar la extensión (?>re). Ver Ruby-Documentation Para más detalles, aquí un Presupuesto:

Esta construcción [..] inhibe la marcha atrás, que puede ser una mejora rendimiento. Por ejemplo, el patrón /a.*b.*a/ toma tiempo exponencial cuando se compara con una cadena que contiene un a seguido de un número de b s, pero sin a. Sin embargo, esto se puede evitar mediante el uso de una expresión regular anidada /a(?>.*b).*a/.

File.open(ARGV.shift) do |f| 
    while line = f.gets 
    if /(.*?)(?> \|.*?SENDING REQUEST.*?TID=)(.*?),/.match(line) 
     puts "#{$1}: #{$2}" 
    end 
    end 
end 
Cuestiones relacionadas