Betrachten Sie diese Regex.
%Vor% Dies wird bei aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac
Dies erfordert 67
Schritte im Debugger zum Fehlschlagen.
Betrachten Sie nun diese Regex.
%Vor% Dies wird im Fall von aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac
Dies erfordert 133
Schritte im Debugger zum Fehlschlagen.
Und schließlich diese Regex:
%Vor% Dies wird im Fall von aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac
Dies erfordert 67
Schritte im Debugger zum Fehlschlagen.
Wenn ich den Benchmark atomic group (?>a*)b
performes 179%
schneller überprüfe.
Jetzt deaktivieren Atomgruppen das Zurückverfolgen. Leistung in Übereinstimmung ist also gut.
Aber warum ist die Anzahl der Schritte mehr? Kann jemand das erklären?
Warum gibt es einen Unterschied? in Schritten zwischen zwei Atomgruppen (?>a*)b
und a*+b
.
Arbeiten sie anders?
Anmerkung des Autors:
Diese Antwort zielt auf Frage 1 ab, die durch den Bounty-Text geliefert wird. "Ich freue mich auf den genauen Grund, warum der Debugger mehr Schritte benötigt. Ich brauche keine Antworten, die erklären, wie atomare Gruppen funktionieren." ; < br> Jerrys Antwort spricht die anderen Bedenken sehr gut an, während meine andere Antwort nimmt eine Fahrt durch die erwähnten Konstrukte, wie sie funktionieren und warum sie wichtig sind. Für das volle Wissen reicht es nicht einfach diesen Beitrag zu lesen!
WAS?!
Ja, ich meine es ernst, lesen Sie weiter ...
Zunächst möchte ich Ihnen quantifizierte Gruppen vorstellen, die nicht gefangen sind, ohne die Gruppe:
%Vor% Also, was genau passiert hier? Wir werden die Muster mit der Testzeichenfolge "concat"
auf einer Regex-Engine mit deaktivierten Optimierungen abgleichen:
Während wir dabei sind, präsentiere ich Ihnen einige weitere Gruppen:
Oh nein! Ich werde vermeiden, Gruppen zu verwenden!
Aber warten Sie! Beachten Sie, dass die Anzahl der Schritte, die zur Übereinstimmung mit unternommen wurden, keine Korrelation mit der Leistung der Übereinstimmung aufweist. pcre Suchmaschinen optimiert entfernt die meisten der "unnötigen Schritte", wie ich bereits erwähnt habe . Atomare Gruppen sind immer noch die effizientesten, obwohl mehr Schritte für eine Engine mit deaktivierten Optimierungen unternommen wurden.
Vielleicht relevant:
Die Engine kommt zu Quantifizierern , die standardmäßig gierig sind. Gierige Modifikatoren passen zu allen möglichen und Backtracks nach Bedarf , was effiziente Treffer ermöglicht,
wie von Gierig vs. widerstrebend vs. Possessive Quantifier verwiesen:
Ein gieriger Quantifizierer passt zuerst so gut wie möglich zusammen. Also stimmt
.*
mit der gesamten Zeichenfolge überein. Dann versucht der Matcher, den folgendenf
anzupassen, aber es sind keine Charaktere mehr übrig. Es "backtracks" also, so dass der gierige Quantifizierer mit einem Ding weniger übereinstimmt (das "o" am Ende des Strings bleibt unberücksichtigt). Das stimmt immer noch nicht mit demf
in der Regex überein, so dass es einen weiteren Schritt "zurückverfolgt", wodurch der gierige Quantifizierer erneut zu einem less thing passt (das "oo" am Ende der Zeichenkette bleibt unberücksichtigt). Das still stimmt nicht mit derf
in der Regex überein, so dass es einen weiteren Schritt zurückverfolgt (wobei "foo" am Ende der Zeichenkette nicht zugeordnet bleibt). Jetzt stimmt der Matcher schließlich mit derf
in der Regex überein, und dieo
und die nächsteo
werden ebenfalls abgeglichen. Erfolg! [...]
a*+b
? In /a*+b/
:
a
Das Literalzeichen "a" . *+
Null oder mehr, Possessiv . b
Das Literalzeichen "b" . Wie von Gierig vs. widerstrebend im Vergleich zu Possessiv-Quantifizierern verwiesen:
Ein possessiver Quantifizierer ist genau wie der gierige Quantifizierer, aber er geht nicht zurück. Es beginnt also damit, dass
.*
mit der gesamten Zeichenfolge übereinstimmt und nichts unübertroffen bleibt. Dann ist nichts mehr übrig für die Übereinstimmung mitf
in der Regex. Da der Possessiv-Quantor nicht zurückverfolgt wird, schlägt die Übereinstimmung fehl.
Die Maschine wird nicht erkennen, ob sie alleine eine (in) effiziente Übereinstimmung erzielt. Sehen Sie sich hier ein anständiges Beispiel an: Programm wird für immer ausgeführt, wenn regulärer Ausdruck gefunden wird . In vielen Szenarios sind Regexes, die schnell geschrieben werden, möglicherweise nicht effizient und können in der Bereitstellung leicht problematisch sein.
Nachdem das Muster in der Atomgruppe die Übereinstimmung beendet hat, wird es nicht mehr loslassen. Studiere dieses Beispiel:
%Vor% Sieht vollkommen legitim aus, aber diese Übereinstimmung schlägt fehl . Die Schritte sind einfach: Der erste Wechsel der Atomgruppe stimmt perfekt überein - \d\w{2}
verbraucht 12c
. Die Gruppe schließt dann ihre Übereinstimmung ab - jetzt ist hier unsere Zeigerposition:
Das Muster wird fortgesetzt. Jetzt versuchen wir, c
zu finden, aber es gibt keine c
. Anstatt zu versuchen, die Zurückverfolgung durchzuführen (Freigabe von \d\w{2}
und konsumieren von 12
), schlägt die Übereinstimmung fehl.
Stellen Sie sich nun vor, wir manipulieren mit einem JSON-Objekt. Diese Datei ist nicht klein. Backtracking vom Ende wird eine schlechte Idee sein.
%Vor%Oh oh ...
Verstehst du, was ich jetzt meine? : P
Ich werde Sie verlassen, um den Rest herauszufinden, und versuchen, mehr über Possessivquantifizierer und Atomgruppen herauszufinden; Ich schreibe nichts anderes in diesen Beitrag. Hier kam der JSON her, ich sah die Antwort vor ein paar Tagen sehr inspirierend: REGEX neuformatieren .
Ich weiß nicht, wie Sie Ihr Benchmarking durchgeführt haben, aber sowohl a*+b
als auch (?>a*)b
sollten gleich sein. Um regulary-expressions.info (Hervorhebung von mir) zu zitieren:
Im Grunde schreiben Sie anstelle von
X*+
(?>X*)
. Es ist wichtig zu beachten, dass sowohl das quantifizierte TokenX
als auch der Quantifizierer innerhalb der Atomgruppe liegen. Auch wennX
eine Gruppe ist, müssen Sie noch eine zusätzliche Atomgruppe um ihn herum platzieren, um denselben Effekt zu erzielen.(?:a|b)*+
entspricht(?>(?:a|b)*)
, nicht jedoch(?>a|b)*
. Letzteres ist ein gültiger regulärer Ausdruck, der jedoch nicht den gleichen Effekt hat, wenn er als Teil eines größeren regulären Ausdrucks verwendet wird.
Und um das oben genannte zu bestätigen, habe ich Folgendes auf ideone ausgeführt:
%Vor%Erhalten dieses als die Ausgabe:
%Vor%Nun, ich bin auf keinen Fall ein PHP-Experte, also weiß ich nicht, ob dies der richtige Weg ist, um Sachen zu bewerten, aber das ist, dass sie alle die gleiche Leistung haben, was angesichts der Einfachheit erwartet wird die Aufgabe.
Dennoch, einige Dinge, die ich aus dem oben genannten bemerke:
Weder (?>a)*b
noch (?>a*)b
sind 179% schneller als ein anderer Regex; die oben genannten sind alle innerhalb von 7% voneinander.
Aber warum ist die Anzahl der Schritte mehr? Kann jemand das erklären?
Es ist zu beachten, dass die Anzahl der Schritte keine direkte Darstellung der Leistung einer Regex ist. Es ist ein Faktor, aber nicht die entscheidende Determinante. Es gibt mehrere Schritte, da die Aufschlüsselung Schritte enthält, bevor Sie die Gruppe betreten, und nachdem Sie die Gruppe ...
eingegeben haben %Vor%Das sind zwei weitere Schritte wegen der Gruppe als die drei Schritte ...
%Vor% Hier können Sie sagen, dass a*b
dasselbe ist wie (?:a*)b
, aber das letztere hat mehr Schritte:
Hinweis: Auch dort sehen Sie, dass regex101 die Schritte für die Anzahl der Schritte in a*b
etwas optimiert hat.
Possessive Quantoren und atomare Gruppen arbeiten unterschiedlich, je nachdem, wie Sie sie verwenden. Wenn ich das Beispiel eines regulären Ausdrucks nehme und es ein wenig verfeinere:
(?>a|b)*ac
Übereinstimmung aabaac
Aber
(?:a|b)*+ac
und (?>(?:a|b)*)ac
stimmen nicht mit aabaac
überein.
Tags und Links regex pcre backtracking