Подтвердить что ты не робот

Регулярное выражение - Ruby vs Perl

Я заметил некоторые крайние задержки в моих сценариях Ruby (1.9), и после некоторого рытья он сводился к регулярному соответствию выражению. Я использую следующие тестовые скрипты в Perl и Ruby:

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

Я использую тот же ввод для обоих скриптов, файл с только 44290 строк. Время для каждого из них:

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

Думаю, я делаю что-то ужасно глупое, любые предложения?

Спасибо

4b9b3361

Ответ 1

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

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

Или

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

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

Ответ 2

Из раздела perlretut: Использование регулярных выражений в Perl - "Поиск и замена"

(Несмотря на то, что регулярное выражение появляется в цикле, Perl достаточно умен, чтобы скомпилировать его только один раз.)

Я не знаю, что Ruby очень хорошо, но я подозреваю, что он компилирует регулярное выражение в каждом цикле.
(Попробуйте код из ответа LaGrandMere, чтобы проверить его).

Ответ 3

Одним из возможных различий является количество выполняемых операций возврата. Perl может улучшить работу по обрезке дерева поиска при обратном отслеживании (т.е. Замечать, когда часть шаблона не может совпадать). Его регулярный двигатель сильно оптимизирован.

Во-первых, добавление ведущего < t20 & raquo; может иметь огромное значение. Если шаблон не совпадает с началом в позиции 0, он не будет совпадать в начальной позиции 1! Поэтому не пытайтесь сопоставлять позицию 1.

Вдоль тех же линий, .*? & raquo; не так ограничивает, как вы могли бы подумать, и заменять каждый экземпляр его более ограничивающим шаблоном может помешать большому отступлению.

Почему бы вам не попробовать:

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

(Не уверен, что было бы безопасно заменить первый ".*?" " < < t24 & & raquo;, поэтому я этого не сделал.)

(По крайней мере, для шаблонов, соответствующих одной строке, (?:(?!PAT).) соответствует PAT, поскольку [^CHAR] соответствует CHAR.)

Использование /s может ускорить работу, если < < < t210 & & raquo; разрешено соответствовать символам новой строки, но я думаю, что это довольно незначительно.

Использование < t211 & & raquo; вместо [space] & raquo; для соответствия пробелу в /x может быть немного быстрее в Ruby. (Они одинаковы в последних версиях Perl.) Я использовал последний, потому что он более читабельный.

Ответ 4

Попробуйте использовать расширение (?>re). Подробнее см. Ruby-Documentation, здесь Цитата:

Эта конструкция [..] запрещает обратное отслеживание, которое может быть повышение производительности. Например, шаблон /a.*b.*a/ принимает экспоненциальное время, когда оно сопоставляется с строкой, содержащей aза которым следует число b s, но без трейлинга a. Однако, этого можно избежать, используя вложенное регулярное выражение /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

Ответ 5

Ruby:

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

Измените метод match на =~. Это быстрее, потому что:

(у Ruby есть Benchmark. Я не знаю вашего содержимого файла, поэтому я случайно набрал что-то)

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)

Отчет о выходе:

       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)

Среднее использует =~. Это занимает менее 1/3 других. Другие два используют метод match. Итак, используйте =~ в своем коде.

Ответ 6

Согласование регулярных выражений занимает много времени по сравнению с другими формами соответствия. Поскольку вы ожидаете длинную статическую строку в середине ваших совпадающих строк, попробуйте отфильтровать строки, которые не включают эту строку, используя относительно дешевые операции с строкой. Это должно привести к тому, что нужно пройти анализ регулярного выражения (в зависимости от того, как выглядит ваш ввод, конечно).

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()

Я не тестировал эту конкретную версию, так как у меня нет ваших входных данных. У меня был успех, делающий такие вещи в прошлом, хотя, особенно с длинными лог файлами, где предварительная фильтрация может устранить подавляющее большинство входных данных без каких-либо регулярных выражений.