warum python regex ist so langsam?
Nach langem Debuggen habe ich herausgefunden, warum meine Anwendung mit python regexps langsam ist. Hier ist etwas finde ich überraschend:
import datetime
import re
pattern = re.compile('(.*)sol(.*)')
lst = ["ciao mandi "*10000 + "sol " + "ciao mandi "*10000,
"ciao mandi "*1000 + "sal " + "ciao mandi "*1000]
for s in lst:
print "string len", len(s)
start = datetime.datetime.now()
re.findall(pattern,s)
print "time spent", datetime.datetime.now() - start
print
Die Ausgabe auf meinem Rechner ist:
string len 220004
time spent 0:00:00.002844
string len 22004
time spent 0:00:05.339580
Den ersten test-string ist 220K lange, übereinstimmt und die Abstimmung ist Recht schnell. Die zweite test-string ist 20Km lang, passt nicht und es dauert 5 Sekunden zu berechnen!
Wusste ich, dass dieser Bericht http://swtch.com/~rsc/regexp/regexp1.html, die besagt, dass die regexp-Implementierung in python, perl, ruby ist etwas nicht optimal... das Ist der Grund? Ich hätte nicht gedacht, dass es passieren könnte, mit einem so einfachen Ausdruck.
Hinzugefügt
Meine ursprüngliche Aufgabe ist das aufteilen einer Zeichenfolge zu versuchen, verschiedene regex wiederum. So etwas wie:
for regex in ['(.*)sol(.*)', '\emph{([^{}])*)}(.*)', .... ]:
lst = re.findall(regex, text)
if lst:
assert len(lst) == 1
assert len(lst[0]) == 2
return lst[0]
Dies ist zu erklären, warum ich nicht verwenden split
. Ich habe mein Problem gelöst durch Austausch (.*)sol(.*)
mit (.*?)sol(.*)
wie vorgeschlagen, durch Martijn.
Wahrscheinlich sollte ich verwenden match
statt findall
... aber ich glaube nicht, das hätte das Problem gelöst, da die regexp wird, um übereinstimmung im gesamten Eingangs-und daher findall sollten aufhören, auf den ersten match.
Trotzdem meine Frage war mehr wie einfach ist es, zu fallen in diesem problem für Sie ein regexp-Neuling... ich denke, es ist nicht so einfach zu verstehen, dass (.*?)sol(.*)
ist die Lösung (und zum Beispiel (.*?)sol(.*?)
ist nicht).
- Nein, der Grund ist nicht die Implementierung. Der Grund dafür ist, dass die beiden
.*
sind zu nachsichtig und zu einer katastrophalen backtracking. Was Sie genau versuchen zu tun? - die katastrophale backtracking ist die Umsetzung problem. Lesen Sie den verlinkten Artikel von Emanuele.
- Nein, die katastrophalen backtracking ist in diesem Fall aufgrund der Muster Konzeption. Sie erhalten mehr oder weniger das gleiche Resultat mit anderen NFA-Motoren.
- Lesen Sie den Artikel. "Die anderen NFA-engines" haben die gleiche Umsetzung. Ein echter FSA würde ohne backtracking.
- Noch eine Anmerkung: das problem ist nicht verursacht durch die doppelte
(.*)
. Suche für(.*)sol
hat genau die gleiche Zeit-Profil. In der Tat(.*)sol
ist eigentlich noch schlimmer, wenn der string enthältsol
und suchen Sie mitfindall()
, denn es löst ein gescheiterter backtracking-Suche auf dem Teilstring, der folgtsol
. (Die original RE konsumieren werden, wird der gesamte string auf Erfolg). - Ich hatte das gleiche Langsamkeit problem - warteten mehr als 2 Minuten, als der string war sehr lang. Ich installiert das Paket
regex
- funktioniert Super! bekam das Ergebnis sofort für die gleiche Zeichenfolge. Heruntergeladen von hier: pypi.python.org/pypi/regex
Du musst angemeldet sein, um einen Kommentar abzugeben.
Den Thompson-NFA-Ansatz wechselt die regulären Ausdrücke aus der Standard-gierig standardmäßig nicht gierig. Normalen regelmäßige Ausdruck-Motoren können das gleiche tun; ändern Sie einfach
.*
zu.*?
. Sie sollten nicht gierig Ausdrücken, wenn nicht-gierig zu tun.Schon jemand gebaut ein NFA regulärer Ausdruck parser für Python: https://github.com/xysun/regex
Es in der Tat besser als der Standard-Python reguläre Ausdrücke parser für die pathologischen Fälle. Jedoch, es unter-führt alles andere:
Festsetzung der pathologischen Fall auf Kosten des typischen ist wahrscheinlich ein guter Grund, nicht zu verwenden, die NFA-Ansatz als Standard-engine, auch nicht, wenn die pathologischen Fall kann einfach vermieden werden, statt.
Ein weiterer Grund ist, dass bestimmte Funktionen (wie Rückverweise) sind entweder sehr schwer oder unmöglich zu realisieren mit der NFA Ansatz. Siehe auch die Antwort auf die Python-Ideen-mailing-Liste.
Als solche, dein test kann gemacht werden viel besser, wenn Sie mindestens eines der Muster, nicht-gierig zu vermeiden, die katastrophale backtracking:
oder nicht mit einem regex an alle, könnten Sie
str.partition()
man die Präfix-und postfix statt:z.B. nicht alle text-Probleme sind reguläre Ausdrücke geprägt, so legte, dass hammer und Blick auf den rest der toolbox!
Zusätzlich, Sie könnte vielleicht an der alternative zu suchen
re
Modul,regex
. Dieses Modul implementiert einige grundlegende Prüfungen für pathologische Fälle, und weicht Ihnen geschickt, ohne Rückgriff auf ein Thompson-NFA-Umsetzung. Zitieren ein Eintrag, um ein Python bug-report trackingregex
:Dieser Motor ausführen kann, Ihre pathologischen Fall mehr als 100 tausend mal schneller:
Hinweis: die zahlen; beschränkte ich
re
test 1 ausführen und es dauerte 2.46 Sekunden, während dieregex
Testläufe 100k mal in unter 2 Sekunden.str.split()
ist, da seine Verwendungfindall()
deutet darauf hin, dass mehrere Positionen gewünscht sind.re.split()
, und dann würden Sie nicht brauchen, um zu verwenden.*
für den Teil vor und nach dersol
text.regex
- Modul? Das ist schnell und viel schneller auf die meisten pathologischen lieben. Für dieses Beispiel ist es mehr als schnell genug. Wie auch immer, ich downvoted, da ich denke, Zeichnung, performance-Schlussfolgerungen aus dem Vergleich einer stark optimierten C-Implementierung mit einem hastig-aus der reinen Python-Implementierung ist über das Schlimmste, das benchmarking, die ich gesehen habe wer tun, immer.regex
Modul überhaupt? Gibt es einen plan, doch wenn es in Python stdlib?(.*)sol
ist, dass es auslösen kann pathologische backtracking.r"\emph{([^{}]*)}(.*)"
(vielleicht sollte ich nicht nennen dies einen "split").Ich denke, das hat nichts zu tun mit katastrophalen backtracking (oder zumindest mein Verständnis davon).
Das problem wird verursacht durch die erste
(.*)
im(.*)sol(.*)
, und die Tatsache, dass die regex ist nicht überall verankert.re.findall()
, nachdem er mit dem index 0, würde sich wiederholen, bei index 1, 2, etc. bis zum Ende der Zeichenfolge.Effektiv hat quadratische Komplexität O(n2), wobei n die Länge der Zeichenkette.
Das problem gelöst werden kann durch eine Verankerung der Ihr Muster, so schlägt es sofort an Positionen, die Ihre Muster keine chance hat, zu entsprechen.
(.*)sol(.*)
suchensol
innerhalb einer Zeile text (mit Trennzeichen, die durch Zeilenende-Zeichen), also, wenn Sie nicht finden können, ein match am Anfang der Zeile, es finden nicht alle für den rest der Zeile.Daher können Sie verwenden:
mit
re.MEHRZEILIG
option.Läuft dieser test-code (modifiziert von Euch):
(Beachten Sie, dass beide übergeben und Versagen sind 220004 Zeichen)
Ergibt Folgendes Ergebnis:
Dadurch wird deutlich, dass in beiden Fällen haben die gleiche Größenordnung.
re.search
ist langsam, währendre.match
ist schnell. Allerdings habe ich versucht, die gleiche Suche mitawk
(aber ich bin mir nicht 100% sicher, dass ich eine gleichwertige Muster) und das scheint fürawk
eine Suche und ein match dauert die gleiche Zeit. Vielleicht ist der Punkt ist, dass mit der NFA Ansatz, den ich implementieren können, die eine Suche in linearer Zeit, wo eine wiederholte entsprechen würde, erfordert quadratische Zeit.Können Sie versuchen, diese.Dies reduziert backtracking-und scheitert schneller.Versuchen Sie Ihren Text hier.
http://regex101.com/r/hQ1rP0/22