Schrift
[thread]8908[/thread]

Regexes: Geschwindigkeitsoptimierung



<< |< 1 2 >| >> 14 Einträge, 2 Seiten
GoodFella
 2007-04-06 19:31
#75719 #75719
User since
2007-01-09
192 Artikel
BenutzerIn
[default_avatar]
Mein Script schafft zu Zeit um die 120 Zeilen / Sekunde ->
Code (perl): (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
            # Im VORNAMEN suchen:
            if (($ba_config{'search_prename'} == 1) and ($ba_config{'vorname_col'} <= $#top_headlines)) # zus.: Wenn Vorname und Nachname NICHT zusammen
             {
              #Titel + Vorname + Namensvorsatz:
              if ($ba_data{'vorname'} =~ /^\s*($search_pattern{'titel'})\s*(.*?)[ ]+($search_pattern{'name_vorsatz'})\s*$/sio)
               {
               
 ($ba_data{'titel'}, $ba_data{'vorname'}, $ba_data{'name_vorsatz'}) = ($1, $2, $3);
               
 push(@ba_info, 'Titel und Namensvorsatz im Vornamen');
               }
              #Titel + Vorname:
              elsif ($ba_data{'vorname'} =~ /^\s*($search_pattern{'titel'})\s*(.*)\s*$/sio)
               {
               
 ($ba_data{'titel'}, $ba_data{'vorname'}) = ($1, $2);
               
 push(@ba_info, 'Titel im Vornamen');
               }
              #Sekundärtitel + Vorname + Namensvorsatz:
              elsif ($ba_data{'vorname'} =~ /^\s*($search_pattern{'titel_secondary'})\s*(.*?)[ ]+($search_pattern{'name_vorsatz'})\s*$/sio)
               {
               
 ($ba_data{'titel_secondary'}, $ba_data{'vorname'}, $ba_data{'name_vorsatz'}) = ($1, $2, $3);
               
 push(@ba_info, 'Sekundärtitel und Namensvorsatz im Vornamen');
               }
              #Sekundärtitel + Vorname:   
              elsif ($ba_data{'vorname'} =~ /^\s*($search_pattern{'titel_secondary'})\s*(.*?)\s*$/sio)
               {
               
 ($ba_data{'titel_secondary'}, $ba_data{'vorname'}) = ($1, $2);
               
 push(@ba_info, 'Sekundärtitel im Vornamen');
               }

..füge ich jedoch dies hier noch mit ein:
Code (perl): (dl )
1
2
3
4
5
6
7
8
              #Vorname + Titel + Namensvorsatz:
              elsif ($ba_data{'vorname'} =~ /^\s*(.*?)[ ]+($search_pattern{'titel'})\s*$search_pattern{'name_vorsatz'}$/sio)
               {
               
 ($ba_data{'vorname'}, $ba_data{'titel'}, $ba_data{'name_vorsatz'}) = ($1, $2, $3);
               
 push(@ba_info, 'Namensvorsatz und Titel im Vornamen');
               }


reduziert sich die Geschwindigkeit auf 50 Zeilen / Sekunde.
Ich habe die Regexes vorher testweise rausgenommen, die Geschwindigkeit hatte sich beim Hinzufügen dieser um max. 5% vermindert; irgendwas an dem letzen Regex jedoch macht das Teil sehr lahm. Habe auch schon mit Benchmark rumgespielt, ohne Erfolg. Den Artikel hier http://www.ccl4.org/~nick/P/Fast_Enough/ habe ich auch schon durch.

Achja, %search_pattern enthält strings der Sorte 'von und zu|van|van den|van der' usw.

Jemand eine Idee, wie ich das schneller machen könnte?\n\n

<!--EDIT|GoodFella|1175873678-->
PerlProfi
 2007-04-06 19:39
#75720 #75720
User since
2006-11-29
340 Artikel
BenutzerIn
[default_avatar]
Vielleicht bringt es ja was ein paar reguläre Ausdrücke in Variablen zu packen, dann müssten sie nicht immer neu ausgewertet werden.

Zum Beispiel könntest du aus:
Code (perl): (dl )
1
2
3
4
5
6
7
8
              #Vorname + Titel + Namensvorsatz:
              elsif ($ba_data{'vorname'} =~ /^\s*(.*?)[ ]+($search_pattern{'titel'})\s*$search_pattern{'name_vorsatz'}$/sio)
               {
               
 ($ba_data{'vorname'}, $ba_data{'titel'}, $ba_data{'name_vorsatz'}) = ($1, $2, $3);
               
 push(@ba_info, 'Namensvorsatz und Titel im Vornamen');
               }


Folgendes machen:
Code (perl): (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
# %search_pattern ist bereits bekannt,
# dieser Teil steht irgendwo, so das er nur einmal ausgeführt wird
my $regexp = qr/^\s*(.*?)[ ]+($search_pattern{'titel'})\s*$search_pattern{'name_vorsatz'}$/sio;

# dann später dein vergleich
#Vorname + Titel + Namensvorsatz:
              elsif ($ba_data{'vorname'} =~ $regexp)
               {
               
 ($ba_data{'vorname'}, $ba_data{'titel'}, $ba_data{'name_vorsatz'}) = ($1, $2, $3);
               
 push(@ba_info, 'Namensvorsatz und Titel im Vornamen');
               }


Bin mir aber nicht sicher ob das wirklich eine große Verbesserung wäre.

MfG
GoodFella
 2007-04-06 20:44
#75721 #75721
User since
2007-01-09
192 Artikel
BenutzerIn
[default_avatar]
[quote=PerlProfi,06.04.2007, 17:39]Vielleicht bringt es ja was ein paar reguläre Ausdrücke in Variablen zu packen, dann müssten sie nicht immer neu ausgewertet werden.

Zum Beispiel könntest du aus:
Code (perl): (dl )
1
2
3
4
5
6
7
8
              #Vorname + Titel + Namensvorsatz:
              elsif ($ba_data{'vorname'} =~ /^\s*(.*?)[ ]+($search_pattern{'titel'})\s*$search_pattern{'name_vorsatz'}$/sio)
               {
               
 ($ba_data{'vorname'}, $ba_data{'titel'}, $ba_data{'name_vorsatz'}) = ($1, $2, $3);
               
 push(@ba_info, 'Namensvorsatz und Titel im Vornamen');
               }


Folgendes machen:
Code (perl): (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
# %search_pattern ist bereits bekannt,
# dieser Teil steht irgendwo, so das er nur einmal ausgeführt wird
my $regexp = qr/^\s*(.*?)[ ]+($search_pattern{'titel'})\s*$search_pattern{'name_vorsatz'}$/sio;

# dann später dein vergleich
#Vorname + Titel + Namensvorsatz:
              elsif ($ba_data{'vorname'} =~ $regexp)
               {
               
 ($ba_data{'vorname'}, $ba_data{'titel'}, $ba_data{'name_vorsatz'}) = ($1, $2, $3);
               
 push(@ba_info, 'Namensvorsatz und Titel im Vornamen');
               }


Bin mir aber nicht sicher ob das wirklich eine große Verbesserung wäre.

MfG[/quote]
Ich werde es probieren, aber: Ist das nicht genau das, was der o-Modifier tut?

[EDIT]
Leider wird das Script dadurch langsamer.

MIT qr// --> 49,1 Zeilen/s
OHNE qr// --> 50,4 Zeilen/s

O_o

Also irgendwie ... versteh einer die Perl Regex Machine...
Habe mal ?: in Gruppierungen eingefügt und die Geschwindigkeit hat sich halbiert. Also ab sofort nutze ich nur noch benchmark dafür und verlasse mich nicht auf meinen logischen Menschenverstand.

Jemand noch eine Idee?\n\n

<!--EDIT|GoodFella|1175879270-->
PerlProfi
 2007-04-06 21:44
#75722 #75722
User since
2006-11-29
340 Artikel
BenutzerIn
[default_avatar]
[quote=GoodFella,06.04.2007, 18:44]Ich werde es probieren, aber: Ist das nicht genau das, was der o-Modifier tut?[/quote]
Da war ich mir bei deinen Regexes nicht so sicher, da du ja Variablen verwendest.
Schließlich kannst du auch
Code: (dl )
1
2
$a = 1;
++$a for 1..999;

nicht zu
Code: (dl )
$a = 1000;

optimieren, weil du nicht weißt was passiert wenn $a einen neuen Wert bekommt.(tie)
GoodFella
 2007-04-06 23:59
#75723 #75723
User since
2007-01-09
192 Artikel
BenutzerIn
[default_avatar]
ehm.. das, was in %search_pattern steht, verändert sich nicht während des Programmablaufs..
darauf wolltest du doch hinaus odeR?
GoodFella
 2007-04-07 05:54
#75724 #75724
User since
2007-01-09
192 Artikel
BenutzerIn
[default_avatar]
Habe noch weitere Patterns getestet, unter anderem folgende Funktion, die die lansgamen \s* am Anfang und Ende meiner Regexes überflüssig machen soll:

Code (perl): (dl )
1
2
3
4
5
6
7
sub remove_wrapping_whitespaces
 {
  my $string = shift;
  $string =~ s/^\s+//;
  $string =~ s/\s+$//;
  return($string);
 }


Was ich ergoogeln konnte, ist das wohl die effektivste Methode dafür. Im Vergleich mit der qr//-Version ist diese aber deutlich schneller:

Code (perl): (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/usr/bin/perl

use strict;
use warnings;
use Benchmark ':all';

my $original = ' Bla Test Jetzt kommen nochmal Leerzeichen         ';
my $string;
my $re1 = qr/^\s+/o;
my $re2 = qr/\s+$/o;

sub one 
 {
  $string = $original;
  $string =~ s/$re1//o;
  $string =~ s/$re2//o;
 }

sub two 
 {
  $string = $original;
  $string =~ s/^\s+//;
  $string =~ s/\s+$//;
 }

cmpthese (200000, { one => sub { &one; }, two => sub { &two; } } );


ergibt:

Quote
---------- Perl ----------
       Rate  one  two
one 261097/s   -- -10%
two 290698/s  11%   --

Output completed (2 sec consumed) - Normal Termination


(Habe mich schon an meine (Teil-)Monologe gewöhnt, sobald es etwas exotischer wird ^^)

[EDIT]
Das Entfernen der Whitespaces am Stringanfang und Ende zusammen mit dem Entfernen der /^\s* .. \s*$/ hat genau den Effekt gehabt, den ich wollte!!! 105 Zeilen pro Sekunde!!! :DDD\n\n

<!--EDIT|GoodFella|1175912293-->
PerlProfi
 2007-04-07 12:07
#75725 #75725
User since
2006-11-29
340 Artikel
BenutzerIn
[default_avatar]
[quote=GoodFella,06.04.2007, 21:59]ehm.. das, was in %search_pattern steht, verändert sich nicht während des Programmablaufs..
darauf wolltest du doch hinaus odeR?[/quote]
Genau, dann macht es nämlich Sinn den regulären Ausdruck nur einmal zu definieren.
Ich war mir da nämlich nicht sicher ob Perl mit dem o modifier nicht trotzdem jedes Mal den Ausdruck neu auswertet, wegen der Variablen.

Naja, hastes ja jetzt so wie du wolltest.

MfG
topeg
 2007-04-07 13:15
#75726 #75726
User since
2006-07-10
2611 Artikel
BenutzerIn

user image
Code (perl): (dl )
$ba_data{'vorname'} =~ /^\s*(.*?)[ ]+($search_pattern{'titel'})\s*$search_pattern{'name_vorsatz'}$/sio

wenn du den Letzten Ausdruck nicht klammerst kann er auch nicht gefunden werden.
Allso
Code (perl): (dl )
$ba_data{'vorname'} =~ /^\s*(.*?) +($search_pattern{'titel'})\s*($search_pattern{'name_vorsatz'})$/sio

Das klammern von einem einzelnen zeichen ist nicht nötig, statt "[ ]" reicht " ".
Da hier nicht mit "g" allso global gesucht wird, macht die Option "o" nichts schadet aber auch nicht.

Ich hätte fdas etwas anders geschrieben.
Das lesen aus seienm String ist etwas schneller als das lesen aus einem Hash. Es ist, meiner Meinung nach (ohne es getestet zu haben), günstiger weniger Regexp zu verwenden. Kurze Variablennamen machen das ganze etwas besser lesbar.
Code (perl): (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
            # Im VORNAMEN suchen:
            my $vorname=$ba_data{'vorname'};
            my $s_ttl=$search_pattern{'titel'};
            my $s_vor=$search_pattern{'name_vorsatz'};
            my $s_sec=$search_pattern{'titel_secondary'};
            if (($ba_config{'search_prename'} == 1) and ($ba_config{'vorname_col'} <= $#top_headlines)) # zus.: Wenn Vorname und Nachname NICHT zusammen
            {
             #Titel + Vorname:
             if ($vorname =~ /^\s*($s_ttl)\s*(.*?)(?: +($s_vor))?\s*$/sio)
             {
              ($ba_data{'titel'}, $ba_data{'vorname'}) = ($1, $2);
              # + Namensvorsatz
              if($3 ne '')
              {
               $ba_data{'name_vorsatz'}=$3;
               push(@ba_info, 'Titel und Namensvorsatz im Vornamen');
              }
              else
              { push(@ba_info, 'Titel im Vornamen'); }
             }
             #Sekundärtitel + Vorname:
             elsif ($vorname =~ /^\s*($s_sec)\s*(.*?)(?: +($s_vor))\s*$/sio)
             {
              ($ba_data{'titel_secondary'}, $ba_data{'vorname'}) = ($1, $2);
              # + Namensvorsatz
              if($3 ne '')
              {
               $ba_data{'name_vorsatz'}=$3;
               push(@ba_info, 'Sekundärtitel und Namensvorsatz im Vornamen');
              }
              else
              { push(@ba_info, 'Sekundärtitel im Vornamen'); }
             }
             #Vorname + Titel + Namensvorsatz:
             elsif ($vorname =~ /^\s*(.*?) +($s_ttl)\s*($s_vor)$/sio)
             {
              ($ba_data{'vorname'}, $ba_data{'titel'}, $ba_data{'name_vorsatz'}) = ($1, $2, $3);
              push(@ba_info, 'Namensvorsatz und Titel im Vornamen');
             }
            }
GoodFella
 2007-04-07 20:32
#75727 #75727
User since
2007-01-09
192 Artikel
BenutzerIn
[default_avatar]
Das mit der vergessenen Klammer ist mir gleich nach dem Posten aufgefallen, hätte es vielleicht korrigieren sollen..

Ich finde "[ ]+" lesbarer als " +", aber ich werde testen, ob es einen Geschwindigkeitsvorteil bringt. Auch das Substituieren des Hashes durch Strings werde ich auf Geschwindigkeit testen.

Ich dachte, der Modifier o wäre dazu da, den Regexp, wenn er denn Variablen enthält, die sich nicht verändern (wie in meinem Fall), dass Perl den Regex nicht immer neu kompiliert; der gepastete Codepart wird nämlich in einer Schleife ausgeführt.

Naja auf jeden Fall danke euch beiden ;)
bloonix
 2007-04-08 22:13
#75728 #75728
User since
2005-12-17
1615 Artikel
HausmeisterIn
[Homepage]
user image
Hallo GoodFella,

[quote=GoodFella,07.04.2007, 18:32]Ich finde "[ ]+" lesbarer als " +", aber ich werde testen, ob es einen Geschwindigkeitsvorteil bringt.[/quote]
was spricht gegen \s? Das verwendest du doch in der selben
Regex, warum also auch nicht hier? Ich würde es schon ulkig finden,
wenn jemand in der gleichen Regex einmal \w und ein wenig
weiter [a-zA-Z_0-9] verwendet. :)

[quote=GoodFella,07.04.2007, 18:32]Ich dachte, der Modifier o wäre dazu da, den Regexp, wenn er denn Variablen enthält, die sich nicht verändern (wie in meinem Fall), dass Perl den Regex nicht immer neu kompiliert; der gepastete Codepart wird nämlich in einer Schleife ausgeführt.[/quote]
Ja, das ist richtig, allerdings sollte man beachten wo der o-Modifier
verwendet wird. Es nutzt sehr wenig, ihn bei der Variablenzuweisung
zu verwenden, was zum Beispiel PerlProfi versucht:

[quote=PerlProfi,06.04.2007, 17:39]Folgendes machen:
Code (perl): (dl )
1
2
3
4
5
6
7
8
# %search_pattern ist bereits bekannt,
# dieser Teil steht irgendwo, so das er nur einmal ausgeführt wird
my $regexp = qr/^\s*(.*?)[ ]+($search_pattern{'titel'})\s*$search_pattern{'name_vorsatz'}$/sio;

# dann später dein vergleich
#Vorname + Titel + Namensvorsatz:
              elsif ($ba_data{'vorname'} =~ $regexp)
               {
[/quote]

Das bringt rein garnichts an Geschwindigkeitsvorteil, da die Variable
$regexp bei jeden Durchlauf neu initialisiert wird. Wenn mich nichts
täuscht, dann wird die Regex sogar immer wieder kompiliert? Aber dafür
lege ich meine Hand nicht ins Feuer. In der Perldoc gibt es zumindest
kein Beispiel, in dem der o-Modifier mit dem qr//-Operator verwendet
wird, sondern immer nur beim Matchen. Das hat wohl auch seine
Gründe:

Code: (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
use strict;
use warnings;
use Benchmark;

Benchmark::cmpthese(-1, {
  bad_1 => \&rx_bad_1,
  bad_2 => \&rx_bad_2,
  good  => \&rx_good
});

sub rx_bad_1 {
  for (0..100000) {
     # wird mit jedem schleifendurchlauf kompiliert?
     my $rx = qr/\d+/o;
     1 =~ /$rx/;
  }
}

sub rx_bad_2 {
  for (0..100000) {
     # wird mit jedem schleifendurchlauf kompiliert
     my $rx = qr/\d+/;
     1 =~ /$rx/o;
  }
}

sub rx_good {
  # so ist es richtig, $rx wird ausserhalb der schleife zugewiesen
  my $rx = qr/\d+/;

  for (0..100000) {
     # und in der schleife nur ein einziges mal kompiliert
     1 =~ /$rx/o;
  }
}


       Rate bad_1 bad_2  good
bad_1 4.39/s    --   -3%  -79%
bad_2 4.50/s    3%    --  -78%
good  20.6/s  369%  356%    --


Um bestmögliche Leistung herauszuholen, solltest du alle Regexes vor
der Schleife definieren und in der Schleife den Modifier /o verwenden,
sobald du versuchst zu matchen, so wie es in rx_good() zu sehen
ist.

Gruss,
opi\n\n

<!--EDIT|opi|1176056271-->
What is a good module? That's hard to say.
What is good code? That's also hard to say.
One man's Thing of Beauty is another's man's Evil Hack.
<< |< 1 2 >| >> 14 Einträge, 2 Seiten



View all threads created 2007-04-06 19:31.