Warum ist meine regex so viel langsamer kompiliert als interpretiert?
Ich habe eine große und komplexe C# regex läuft OK, wenn Sie interpretiert wird, aber ist ein bisschen langsam. Ich versuche, speed-up durch die Einstellung RegexOptions.Compiled
, und das scheint in 30 Sekunden zum ersten mal, und sofort danach. Ich bin versucht zu negieren, dies durch kompilieren der regex zu einer ersten Versammlung, so dass meine app so schnell wie möglich.
Mein problem ist, wenn die Erstellung Verzögerung erfolgt, ob es kompiliert in der app:
Regex myComplexRegex = new Regex(regexText, RegexOptions.Compiled);
MatchCollection matches = myComplexRegex.Matches(searchText);
foreach (Match match in matches) //<--- when the one-time long delay kicks in
{
}
oder mit Regex.CompileToAssembly im Voraus:
MatchCollection matches = new CompiledAssembly.ComplexRegex().Matches(searchText);
foreach (Match match in matches) //<--- when the one-time long delay kicks in
{
}
Dadurch wird die Kompilierung einer assembly im Grunde nutzlos, da ich immer noch die Verzögerung auf die erste foreach
nennen. Was ich will, ist für alle kompilieren Verzögerung getan werden zur compile-Zeit statt (an der Regex.CompileToAssembly nennen), und nicht zur Laufzeit. Wo mache ich falsch ?
(Der code, den ich verwende, um zu kompilieren, um eine Montage ist ähnlich http://www.dijksterhuis.org/regular-expressions-advanced/ , falls das relevant ist ).
Edit:
Soll ich mich mit new
beim Aufruf der kompilierten assembly in new CompiledAssembly.ComplexRegex().Matches(searchText);
? Es gibt ein "Objektverweis erforderlich" - Fehler, ohne es aber.
Update 2
Danke für die Antworten/Kommentare. Die regex, die ich verwende, ist ziemlich lang, aber im Grunde einfach, eine Liste von tausenden von Wörtern, jeweils durch | getrennt. Ich kann nicht sehen, es wäre ein backtracking problem wirklich. Die Zeichenkette kann nur einen Buchstaben lang und es kann noch verursachen die Zusammenstellung Verzögerung. Für ein RegexOptions.Zusammengestellt regex, es dauert über 10 Sekunden ausgeführt werden, wenn die regex enthält 5000 Wörter. Zum Vergleich die nicht-kompilierte version der regex nehmen kann, mit 30.000+ Wörter-und immer noch ausführen, nur um sofort.
Nachdem ich eine Menge Tests, die auf dieser, ich denke, was ich herausgefunden habe ist:
- Nicht verwenden, RegexOptions.Kompiliert wenn dein regex hat viele alternativen - es kann extrem langsam sein, um zu kompilieren.
- .Net verwenden lazy evaluation für regex, wenn möglich, und AFAI sehen kann, dieses erstreckt sich (zumindest teilweise) zu regex-Kompilierung zu. Ein regex vollständig kompiliert nur, wenn es sein muss, und es scheint keinen Weg zu zwingen, Zusammenstellung vor der Zeit.
- Regex.CompileToAssembly wäre viel nützlicher, wenn die regexes könnte gezwungen sein, vollständig zusammengestellt, es scheint zu sein, gesteigert wird sinnlos, wie es ist.
Bitte korrigieren Sie mich, wenn ich falsch bin oder etwas fehlt!
- Vielleicht sollten Sie versuchen, auf die tatsächliche expression beteiligt und ein Beispiel für die Eingabe, die Ihnen dieses Verhalten.
- Vielen Dank für diesen Beitrag. Hatte das gleiche Problem mit einigen Regex von Twitter portiert von Java nach .NET. Beide RegexOptions.Zusammengestellt und .CompileToAssembly verursacht die Anwendung nicht mehr reagiert für ~10 Sekunden beim ersten mal, wenn er versucht zu entsprechen. Entfernt die Regex.Kompiliert und alles ist instant.
- MSDN Hinzugefügt, "best practices" -Artikel .NET 4 die angesprochen, regex zusammengestellt, die sich zu Baugruppen.
Du musst angemeldet sein, um einen Kommentar abzugeben.
Bei der Verwendung
RegexOptions.Compiled
Sie sollten sicherstellen, dass der re-benutzen Sie das Regex-Objekt. Es scheint nicht, wie Sie dies tun.RegexOptions.Compiled
ist ein trade-off. Der erste Bau der Regex langsamer, da der code kompiliert wird on-the-fly, aber jedes Spiel sollte schneller sein. Wenn Ihr regulärer Ausdruck änderungen zur Laufzeit, wird es wahrscheinlich keinen nutzen aus der Verwendung von RegexOptions.Kompiliert, obwohl es vielleicht, hängt von der tatsächlichen expression beteiligt.Update, pro die Kommentare
Wenn Ihr code sieht aus wie die, die Sie gebucht haben, werden Sie nicht die Einnahme Vorteil
CompileToAssembly
, als Sie erschaffen neue, on-the-fly kompiliert Instanzen von Regex jedes mal das Stück code ausgeführt wird. Um die Vorteile von CompileToAssembly, Sie benötigen zum kompilieren der Regex den ersten, dann nehmen Sie die generierte assembly und Verweis in Ihrem Projekt. Sie sollten dann das instanziieren des erzeugten, stark typisierte Regex-Typen generiert.In dem Beispiel, das Sie verlinken, ist er ein regulärer Ausdruck mit dem Namen FindTCPIP, die kompiliert wird in einem Typ mit dem Namen FindCTPIP. Wenn diese benutzt werden muss, sollte man eine neue Instanz erstellen, diese bestimmten Typ, z.B.:
Versuchen Sie es mit Regex.CompileToAssembly, dann link zu der Baugruppe, so dass Sie bauen können die Regex-Objekte.
RegexOptions.Compiled
ist eine runtime-option, die regex würde noch ein re-kompiliert werden jedes mal, wenn Sie die Anwendung ausführen.new Regex
, dann sind Sie der Bau einer neuen, unkompilierte, Regex-Instanzen. Sie müssen verwenden Sie die Klassen in Ihrem regex-Montage.Eine sehr wahrscheinliche Ursache bei der Untersuchung einer langsam regex ist, dass es backtracks zu viel. Dieses Problem ist durch umschreiben der regex so, dass die Anzahl der backtracking ist nicht vorhanden oder minimal.
Kannst du die regex und ein Beispiel für die Eingabe, wo es langsam ist.
Ich persönlich habe nicht das Bedürfnis haben, die Erstellung eines regex-obwohl seine interessant zu sehen, einige tatsächliche zahlen über die Leistung, wenn Sie getroffen haben, diesen Weg.
(?>subexpression)
element Sprache. Mehr Informationen zu diesem Thema finden Sie in der Beste Methoden für Reguläre Ausdrücke in .NET-Framework auf der MSDN-Website.Zu zwingen, die Initialisierung können Sie callen gegen einen leeren string. Auf top von, dass Sie verwenden können, ngen zu erstellen, die eine native Bild des Ausdrucks, den Prozess zu beschleunigen sogar noch mehr. Aber wahrscheinlich am wichtigsten ist, es ist im Grunde nur so schnell zu werfen, 30.000-string.IndexOf ist oder string.Enthält oder Regex.Match-Anweisungen für einen bestimmten text, als kompilieren eine riesige big-Ausdruck zu Match gegen einen einzelnen text. Seit dieser erfordert viel weniger Zusammenstellung, jitting etc, da der Zustand der Maschine ist viel einfacher.
Andere Sache, die Sie könnte in Erwägung ziehen, ist für die tokenisierung der Texte und schneiden Sie es mit der Liste der Wörter, die Sie suchen.
Nach umfangreichen Tests meiner eigenen, kann ich bestätigen, der Verdacht der mikel sind im wesentlichen korrekt. Auch wenn mit
Regex.CompileToAssembly()
und statisch gelinkt werden, die resultierende DLL in die Anwendung, es gibt eine erhebliche Verzögerung auf die erste praktische matching-call (zumindest für Muster mit vielen ORed alternativen). Außerdem sind die ersten Verzögerung auf die erste matching-call hängt davon ab, was text, den Sie match gegen. Zum Beispiel, Abgleich gegen eine leere Zeichenfolge oder einen anderen beliebigen text wird dazu führen, dass weniger von einer anfänglichen Verzögerung, aber Sie werden immer noch zusätzliche Verzögerungen später, wenn die tatsächlichen positive Treffer sind zum ersten mal begegnet in den neuen text ein. Der einzige Weg, um vollständig Garantie für zukünftige Spiele wird alles Blitz schnell ist, um zunächst Kraft eine positive übereinstimmung zur Laufzeit mit text, der tatsächlich übereinstimmen. Natürlich gibt die maximale Verzögerung möglich (im Austausch für alle zukünftigen matches wird der Blitz schnell).Ich tiefer gegraben, um das besser verstehen. Für jeden regex kompiliert in der Montage, der ein Tripel von Klassen geschrieben werden, mit der folgenden Benennung der Vorlage: {
RegexName
,RegexNameFactoryN
,RegexNameRunnerN
}. Ein Verweis auf dieRegexNameFactoryN
Klasse instanziiert wird, wird zum Zeitpunkt derRegexName
ctor, aber derRegexNameRunnerN
Klasse nicht. Finden Sie privatefactory
undrunnerref
Felder in der BasisRegex
Klasse.runnerref
ist ein Cache schwachen Verweis auf eineRegexNameRunnerN
Objekt. Nach diversen Experimenten mit der Reflexion kann ich bestätigen, dass die ctors alle 3 dieser kompilierten Klassen sind schnell und dieRegexNameFactoryN.CreateInstance()
- Funktion (liefert die ersteRegexNameRunnerN
Referenz) ist auch schnell. Die anfängliche Verzögerung tritt irgendwo innerhalbRegexRunner.Scan()
oder rufen Sie Baum, und ist somit wahrscheinlich außerhalb der Reichweite des MSIL kompiliert, erzeugt durchRegex.CompileToAssembly()
da dieser Aufruf-Baum beinhaltet zahlreiche nicht-abstrakte Funktionen. Dies ist sehr bedauerlich und bedeutet, dass die C# Regex-Kompilierung performance-Vorteile, die nur so weit: Zur Laufzeit wird es immer einige erhebliche Verzögerung in der ersten Zeit eine positive übereinstimmung gefunden wird (zumindest für diese Klasse von vielen-ORed Muster).Ich vermuten, dass dies hat zu tun mit, wie der Nichtdeterministische Endliche Automat (NFA) Motor führt es die eigenen internen caching/Instanzen zur Laufzeit als Muster verarbeitet.
jessehouwing's Vorschlag ngen ist interessant und könnte möglicherweise die Leistung zu verbessern. Ich habe es nicht getestet.