seth_not@home: regexp-geschwindigkeit

Beitrag lesen

gudn tach!

klaert mich bitte mal auf - welcher ausdruck wuerde denn nach der theorie von
   einer regex-engine performanter verarbeitet werden - der von mir ueberarbeitete,
   oder seths dahingeperlter - (/^(?:2(?:5[0-5]|[0-4]\d)|(?:1\d|[1-9])?\d)$/) - und
   vor allem, warum?

puh, also das kommt sehr darauf an, wie die strings aussehen, auf welche die ausdruecke losgelassen werden.

ein regexp-parser arbeitet den ausdruck von links nach rechts ab, und schaut jeweils, ob's passt. der string wird ebenfalls von links nach rechts abgearbeitet; wobei wird dort u.u. wieder an den anfang des strings gesprungen. ebenso kann beim regexp durch backtracking (siehe z.b. wikipedia) gesprungen werden.

am bsp. wird's klarer:
string = "7":

regexp = /^[0-9]$|^[1-9][0-9]$|^1[0-9][0-9]$|^2[0-4][0-9]$|^25[0-5]$/

^     matcht den anfang
  [0-9] matcht 7
  $     matcht das ende
  fertig

das gleiche in umgekehrter reihenfolge, also
regexp = /^25[0-5]$|^2[0-4][0-9]$|^1[0-9][0-9]$|^[1-9][0-9]$|^[0-9]$/

^       matcht den anfang
  2       matcht nicht
  5[0-5]$ erst recht nicht
  |       vergesse bisher gematchtes
  ^       matcht den anfang
  2       usw.
  usw.  bis halt irgendwann...
  ^       matcht den anfang
  [0-9]   matcht die 7
  $       matcht das ende
  endlich fertig

und jetzt meins:
regexp = /^(?:2(?:5[0-5]|[0-4]\d)|(?:1\d|[1-9])?\d)$/

^       matcht den anfang
  2       matcht nicht
  |       vergiss die "2", veraendere den pointer im string aber nicht
  1       matcht nicht
  |
  [0-9]   matcht 7
  ?       wird vorerst ignoriert, aber falls spaeter etwas nicht gematcht wird, wird gebacktracked
  \d      matcht nicht!
          backtracking (gematchte '7' wird vergessen)
  \d      matcht '7'
  $       matcht das ende
  endlich fertig

so in etwa.

das backtracking frisst sehr viel zeit. sieht man auch beim benchmarking:

#!/usr/bin/perl  
use strict;  
use Benchmark qw(:all) ;  
my $s = '7';  
my $results = timethese(5_000_000, {  
  'schnell' => sub{$s=~/^[0-9]$|^[1-9][0-9]$|^1[0-9][0-9]$|^2[0-4][0-9]$|^25[0-5]$/;},  
  'lahm'    => sub{$s=~/^25[0-5]$|^2[0-4][0-9]$|^1[0-9][0-9]$|^[1-9][0-9]$|^[0-9]$/;},  
  'seth'    => sub{$s=~/^(?:2(?:5[0-5]|[0-4]\d)|(?:1\d|[1-9])?\d)$/;},  
  'seth2'   => sub{$s=~/^(?:2(?:5[0-5]|[0-4]\d)|(?:1\d|[1-9])\d|\d)$/;},  
 },'none'  
);  
cmpthese($results);

fuehrt bei mir zu
("7")
             Rate    seth    lahm   seth2 schnell
seth    1016260/s      --    -34%    -42%    -59%
lahm    1533742/s     51%      --    -12%    -38%
seth2   1742160/s     71%     14%      --    -30%
schnell 2487562/s    145%     62%     43%      --

ist natuerlich relativ ungenau, aber nicht zu sehr.
hier spiegelt sich in etwa das wieder, was ich oben beschrieben habe.
"seth2" ist derselbe ausdruck wie "seth" mit dem unterschied, dass das backtracking ansichtlich umgangen wird.

bei einem laengeren string dagegen, also z.b. "250", waere "schnell" lahm und "lahm" schnell:

("250")
            Rate schnell    seth    lahm   seth2
schnell 1445087/s      --    -20%    -27%    -33%
seth    1798561/s     24%      --     -9%    -16%
lahm    1968504/s     36%      9%      --     -8%
seth2   2145923/s     48%     19%      9%      --

da hier backtracking gar nicht noetig waere, waere "schnell" sogar der lahmste, wobei die unterschiede hier nicht ganz so gross waeren.

sorry, dass ich's nur so grob erklaert habe, auf papier waer's einfacher und wuerde nicht so viel arbeit machen. ;-)

prost
seth