Warum ist eine Cache-Regexp besser als eine kompilierte?
-
06-07-2019 - |
Frage
Dies ist nur eine Frage, um meine Neugierde zu befriedigen.Aber für mich ist es interessant.
Ich schrieb dieses kleine, einfache benchmark.Es fordert 3 Variante (N) der Regexp-Ausführung in zufälliger Reihenfolge ein paar tausend mal:
Im Grunde habe ich das gleiche Muster verwenden, aber auf unterschiedliche Weise.
Ihre gewöhnliche Art und Weise ohne
RegexOptions
.Beginnend mit .NET 2.0 diese nicht zwischengespeichert werden.Sollte aber "im Cache", denn es ist gehalten in eine ziemlich Globale Reichweite und nicht zurückgesetzt.Mit
RegexOptions.Compiled
Durch einen Aufruf der statischen
Regex.Match(pattern, input)
die zwischengespeichert .NET 2.0
Hier ist der code:
static List<string> Strings = new List<string>();
static string pattern = ".*_([0-9]+)\\.([^\\.])$";
static Regex Rex = new Regex(pattern);
static Regex RexCompiled = new Regex(pattern, RegexOptions.Compiled);
static Random Rand = new Random(123);
static Stopwatch S1 = new Stopwatch();
static Stopwatch S2 = new Stopwatch();
static Stopwatch S3 = new Stopwatch();
static void Main()
{
int k = 0;
int c = 0;
int c1 = 0;
int c2 = 0;
int c3 = 0;
for (int i = 0; i < 50; i++)
{
Strings.Add("file_" + Rand.Next().ToString() + ".ext");
}
int m = 10000;
for (int j = 0; j < m; j++)
{
c = Rand.Next(1, 4);
if (c == 1)
{
c1++;
k = 0;
S1.Start();
foreach (var item in Strings)
{
var m1 = Rex.Match(item);
if (m1.Success) { k++; };
}
S1.Stop();
}
else if (c == 2)
{
c2++;
k = 0;
S2.Start();
foreach (var item in Strings)
{
var m2 = RexCompiled.Match(item);
if (m2.Success) { k++; };
}
S2.Stop();
}
else if (c == 3)
{
c3++;
k = 0;
S3.Start();
foreach (var item in Strings)
{
var m3 = Regex.Match(item, pattern);
if (m3.Success) { k++; };
}
S3.Stop();
}
}
Console.WriteLine("c: {0}", c1);
Console.WriteLine("Total milliseconds: " + (S1.Elapsed.TotalMilliseconds).ToString());
Console.WriteLine("Adjusted milliseconds: " + (S1.Elapsed.TotalMilliseconds).ToString());
Console.WriteLine("c: {0}", c2);
Console.WriteLine("Total milliseconds: " + (S2.Elapsed.TotalMilliseconds).ToString());
Console.WriteLine("Adjusted milliseconds: " + (S2.Elapsed.TotalMilliseconds*((float)c2/(float)c1)).ToString());
Console.WriteLine("c: {0}", c3);
Console.WriteLine("Total milliseconds: " + (S3.Elapsed.TotalMilliseconds).ToString());
Console.WriteLine("Adjusted milliseconds: " + (S3.Elapsed.TotalMilliseconds*((float)c3/(float)c1)).ToString());
}
Jedes mal, wenn ich es nennen, das Ergebnis ist entlang der Linien von:
Not compiled and not automatically cached: Total milliseconds: 6185,2704 Adjusted milliseconds: 6185,2704 Compiled and not automatically cached: Total milliseconds: 2562,2519 Adjusted milliseconds: 2551,56949184038 Not compiled and automatically cached: Total milliseconds: 2378,823 Adjusted milliseconds: 2336,3187176891
So dort haben Sie es.Nicht viel, aber etwa 7-8% Unterschied.
Es ist nicht das einzige Geheimnis.Ich kann nicht erklären, warum die erste Möglichkeit wäre, dass sehr viel langsamer, denn es ist nie neu bewertet, hielt aber in eine Globale statische variable.
By the way, ist dies auf .Net 3.5 und Mono 2.2 das Verhalten genau das gleiche.Auf Windows.
Also, irgendwelche Ideen, warum die kompilierte Variante sogar in Rückstand geraten?
EDIT1:
Nach der Befestigung der code, der die Ergebnisse nun wie folgt Aussehen:
Not compiled and not automatically cached: Total milliseconds: 6456,5711 Adjusted milliseconds: 6456,5711 Compiled and not automatically cached: Total milliseconds: 2668,9028 Adjusted milliseconds: 2657,77574842168 Not compiled and automatically cached: Total milliseconds: 6637,5472 Adjusted milliseconds: 6518,94897724836
Die ziemlich viel obsoletes all die anderen Fragen.
Vielen Dank für die Antworten.
Lösung
In der Regex.Match version, die Sie suchen, die Eingang in die Muster.Tauschen Sie die Parameter um.
var m3 = Regex.Match(pattern, item); // Wrong
var m3 = Regex.Match(item, pattern); // Correct
Andere Tipps
Ich bemerkte ein ähnliches Verhalten.Ich fragte mich auch, warum die kompilierte version wäre langsamer, aber bemerkt, dass über eine bestimmte Anzahl von anrufen, die kompilierte version schneller ist.Also grub ich in Reflektor ein wenig, und ich bemerkte, dass für eine kompilierte Regex, es ist immer noch ein wenig setup, die durchgeführt wird, auf den ersten Ruf (speziell, erstellen Sie eine Instanz des entsprechenden RegexRunner
Objekt).
In meinem test habe ich festgestellt, dass wenn ich zog beide an den Konstruktor und eine erste Wegwerf-Aufruf der regex außerhalb der timer starten, das kompilierte regex gewonnen, egal, wie viele Iterationen ich lief.
Übrigens, die Zwischenspeicherung, der Rahmen ist zu tun, wenn statische Regex
Methoden ist eine Optimierung ist nur erforderlich, wenn statische Regex
Methoden.Das ist, weil jeder Aufruf der statischen Regex
- Methode erstellt ein neues Regex
Objekt.In der Regex
Klasse Konstruktor muss es analysieren Sie die Muster.Das Zwischenspeichern ermöglicht es, nachfolgende Aufrufe von statischen Regex
Methoden zur Wiederverwendung der RegexTree
analysiert von den ersten Anruf, um dadurch den parsing-Schritt.
Wenn Sie mit Instanz-Methoden auf einer einzigen Regex
Objekt, dann ist dies kein Problem.Die Analyse ist immer noch nur einmal durchgeführt (wenn Sie das Objekt erstellen).Darüber hinaus erhalten Sie zu vermeiden, laufen alle anderen code in den Konstruktor sowie die heap-Zuweisung (und nachfolgende garbage collection).
Martin Braun bemerkt dass Sie Umgekehrt die Argumente an Ihre statischen Regex
call (guter Fang, Martin).Ich denke, Sie werden feststellen, dass, wenn Sie fix, dass die Instanz (nicht kompiliert) - regex-schlagen wird, die statische Anrufe zu jeder Zeit.Sie sollten auch feststellen, dass, in Anbetracht meiner Ergebnisse vor, die erstellten Instanz schlagen wird, die nicht kompiliert auch.
ABER:Sollten Sie wirklich Lesen Jeff Atwood ' s post auf kompilierte regexes, bevor Sie gehen blind anwenden, die option zu jedem regex, die Sie erstellen.
Wenn Sie immer den gleichen string mit dem gleichen Muster, die erklären, warum eine zwischengespeicherte version ist leicht schneller als eine kompilierte version.
Dies ist aus der Dokumentation;
https://msdn.microsoft.com/en-us/library/gg578045(v=vs. 110).aspx
wenn ein static regular expression Methode wird aufgerufen, und die regelmäßige Ausdruck kann nicht gefunden werden in der cache, die engine für reguläre Ausdrücke konvertiert den regulären Ausdruck, um eine Reihe von Betriebs-codes und speichert Sie Sie im cache.Anschließend konvertiert diese operation codes MSIL so dass der JIT-compiler können Sie ausführen kann. Interpretiert regelmäßige Ausdrücke verringern die Startzeit auf Kosten der langsameren Ausführung Zeit.Weil dieser, Sie sind am besten verwendet, wenn der reguläre Ausdruck ist in einer kleinen Anzahl von Methodenaufrufen, oder, wenn die genaue Anzahl der Aufrufe regulären Ausdruck Methoden ist unbekannt, aber wird erwartet, klein.Als die Anzahl der Methodenaufrufe erhöht, die Leistung gain von der reduzierten startup-Zeit ist überholt durch die langsamere Ausführung Geschwindigkeit.
Im Gegensatz zu interpretierten regulären Ausdrücken kompilierte reguläre Ausdrücke erhöhen die Startzeit execute einzelnen pattern-matching-Methoden schneller.Als Ergebnis, die Leistung nutzen , dass Ergebnisse aus der Zusammenstellung der reguläre Ausdruck erhöht in Verhältnis zu der Anzahl der reguläre Ausdruck Methoden genannt.
Um zusammenzufassen, wir empfehlen, dass Sie verwenden interpretiert reguläre Ausdrücke beim Aufruf regulären Ausdruck Methoden, die mit einem bestimmten regular expression relativ selten.
Sollten Sie benutzen kompilierte reguläre Ausdrücke wenn Sie regelmäßigen Anruf Ausdruck Methoden, die mit einem bestimmten regulären Ausdruck relativ Häufig.
Wie zu erkennen?
Die genaue Schwelle, bei der die langsamere Ausführungsgeschwindigkeit von interpretiert reguläre Ausdrücke überwiegen Gewinne aus Ihren reduzierten Start-Zeit, oder die Schwelle, bei der die langsamere Zeiten starten kompilierte reguläre Ausdrücke überwiegen Gewinne aus Ihren schneller Ausführungsgeschwindigkeit, ist schwer zu bestimmen.Es hängt von einer Vielzahl von Faktoren ab, einschließlich der Komplexität des regulären Ausdrucks und die bestimmte Daten, die er verarbeitet. Um zu bestimmen, ob interpretiert oder kompilierte reguläre Ausdrücke bieten die beste Leistung für Ihr Besondere Anwendung Szenario können Sie die Stopwatch-Klasse vergleichen Sie die Ausführungszeiten.
Kompilierte Reguläre Ausdrücke:
Wir empfehlen Ihnen, kompilieren Sie reguläre Ausdrücke, um eine assembly in die folgenden Situationen:
- Wenn Sie ein Komponenten-Entwickler, der will zum erstellen einer Bibliothek wiederverwendbarer reguläre Ausdrücke.
- Wenn Sie erwarten, dass Ihre regular expression pattern-matching-Methoden genannt zu werden, ein unbestimmten Anzahl von mal-überall von einmal oder zweimal, um Tausende oder Zehntausende mal.Im Gegensatz zu kompilierten oder interpretiert, reguläre Ausdrücke, reguläre Ausdrücke kompiliert werden separate Baugruppen bieten Leistung, die konsistent ist, unabhängig von der Anzahl der Methodenaufrufe.