Wie funktioniert RegexOptions.Compiled?

168

Was passiert hinter den Kulissen, wenn Sie einen regulären Ausdruck als zu kompilierenden markieren? Wie unterscheidet sich dieser Vergleich von einem zwischengespeicherten regulären Ausdruck?

Wie können Sie anhand dieser Informationen feststellen, wann die Berechnungskosten im Vergleich zur Leistungssteigerung vernachlässigbar sind?

Bob
quelle
Gute Ressource zu Best Practices für Regex: docs.microsoft.com/en-us/dotnet/standard/base-types/…
CAD-

Antworten:

302

RegexOptions.CompiledWeist die Engine für reguläre Ausdrücke an, den Ausdruck für reguläre Ausdrücke mithilfe der Lightweight Code Generation ( LCG ) in IL zu kompilieren . Diese Kompilierung erfolgt während der Konstruktion des Objekts und verlangsamt es erheblich . Übereinstimmungen mit dem regulären Ausdruck sind wiederum schneller.

Wenn Sie dieses Flag nicht angeben, wird Ihr regulärer Ausdruck als "interpretiert" betrachtet.

Nehmen Sie dieses Beispiel:

public static void TimeAction(string description, int times, Action func)
{
    // warmup
    func();

    var watch = new Stopwatch();
    watch.Start();
    for (int i = 0; i < times; i++)
    {
        func();
    }
    watch.Stop();
    Console.Write(description);
    Console.WriteLine(" Time Elapsed {0} ms", watch.ElapsedMilliseconds);
}

static void Main(string[] args)
{
    var simple = "^\\d+$";
    var medium = @"^((to|from)\W)?(?<url>http://[\w\.:]+)/questions/(?<questionId>\d+)(/(\w|-)*)?(/(?<answerId>\d+))?";
    var complex = @"^(([^<>()[\]\\.,;:\s@""]+"
      + @"(\.[^<>()[\]\\.,;:\s@""]+)*)|("".+""))@"
      + @"((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}"
      + @"\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+"
      + @"[a-zA-Z]{2,}))$";


    string[] numbers = new string[] {"1","two", "8378373", "38737", "3873783z"};
    string[] emails = new string[] { "[email protected]", "sss@s", "[email protected]", "[email protected]" };

    foreach (var item in new[] {
        new {Pattern = simple, Matches = numbers, Name = "Simple number match"},
        new {Pattern = medium, Matches = emails, Name = "Simple email match"},
        new {Pattern = complex, Matches = emails, Name = "Complex email match"}
    })
    {
        int i = 0;
        Regex regex;

        TimeAction(item.Name + " interpreted uncached single match (x1000)", 1000, () =>
        {
            regex = new Regex(item.Pattern);
            regex.Match(item.Matches[i++ % item.Matches.Length]);
        });

        i = 0;
        TimeAction(item.Name + " compiled uncached single match (x1000)", 1000, () =>
        {
            regex = new Regex(item.Pattern, RegexOptions.Compiled);
            regex.Match(item.Matches[i++ % item.Matches.Length]);
        });

        regex = new Regex(item.Pattern);
        i = 0;
        TimeAction(item.Name + " prepared interpreted match (x1000000)", 1000000, () =>
        {
            regex.Match(item.Matches[i++ % item.Matches.Length]);
        });

        regex = new Regex(item.Pattern, RegexOptions.Compiled);
        i = 0;
        TimeAction(item.Name + " prepared compiled match (x1000000)", 1000000, () =>
        {
            regex.Match(item.Matches[i++ % item.Matches.Length]);
        });

    }
}

Es führt 4 Tests mit 3 verschiedenen regulären Ausdrücken durch. Zuerst wird ein einzelnes einmaliges Spiel getestet (kompiliert gegen nicht kompiliert). Zweitens werden Wiederholungsübereinstimmungen getestet, bei denen derselbe reguläre Ausdruck wiederverwendet wird.

Die Ergebnisse auf meinem Computer (in der Version kompiliert, kein Debugger angehängt)

1000 einzelne Übereinstimmungen (Regex konstruieren, abgleichen und entsorgen)

Geben Sie | ein Plattform | Trivial Number | Einfache E-Mail-Prüfung | Ext Email Check
-------------------------------------------------- ----------------------------
Interpretiert | x86 | 4 ms | 26 ms | 31 ms
Interpretiert | x64 | 5 ms | 29 ms | 35 ms
Zusammengestellt | x86 | 913 ms | 3775 ms | 4487 ms
Zusammengestellt | x64 | 3300 ms | 21985 ms | 22793 ms

1.000.000 Übereinstimmungen - Wiederverwendung des Regex-Objekts

Geben Sie | ein Plattform | Trivial Number | Einfache E-Mail-Prüfung | Ext Email Check
-------------------------------------------------- ----------------------------
Interpretiert | x86 | 422 ms | 461 ms | 2122 ms
Interpretiert | x64 | 436 ms | 463 ms | 2167 ms
Zusammengestellt | x86 | 279 ms | 166 ms | 1268 ms
Zusammengestellt | x64 | 281 ms | 176 ms | 1180 ms

Diese Ergebnisse zeigen, dass kompilierte reguläre Ausdrücke in Fällen, in denen Sie das Objekt wiederverwenden , bis zu 60% schneller sein können Regex. In einigen Fällen kann die Konstruktion jedoch um mehr als 3 Größenordnungen langsamer sein.

Es zeigt auch, dass die x64-Version von .NET beim Kompilieren regulärer Ausdrücke fünf- bis sechsmal langsamer sein kann.


Die Empfehlung wäre, die kompilierte Version in Fällen zu verwenden, in denen dies auch der Fall ist

  1. Sie interessieren sich nicht für die Kosten der Objektinitialisierung und benötigen die zusätzliche Leistungssteigerung. (Beachten Sie, dass es sich hier um Bruchteile einer Millisekunde handelt.)
  2. Sie interessieren sich ein wenig für die Initialisierungskosten, verwenden das Regex-Objekt jedoch so oft wieder, dass es dies während Ihres Anwendungslebenszyklus kompensiert.

Spanner in Arbeit, der Regex-Cache

Die Engine für reguläre Ausdrücke enthält einen LRU-Cache, der die letzten 15 regulären Ausdrücke enthält, die mit den statischen Methoden für die RegexKlasse getestet wurden .

Zum Beispiel: Regex.Replace, Regex.Matchetc .. verwenden alle die Regex - Cache.

Die Größe des Caches kann durch Einstellen erhöht werden Regex.CacheSize. Es akzeptiert jederzeit Größenänderungen während des Lebenszyklus Ihrer Anwendung.

Neue reguläre Ausdrücke werden nur von den statischen Helfern der Regex-Klasse zwischengespeichert. Wenn Sie Ihre Objekte erstellen, wird der Cache überprüft (auf Wiederverwendung und Unebenheiten). Der reguläre Ausdruck, den Sie erstellen, wird jedoch nicht an den Cache angehängt .

Dieser Cache ist ein trivialer LRU-Cache, der mithilfe einer einfachen doppelt verknüpften Liste implementiert wird. Wenn Sie es auf 5000 erhöhen und 5000 verschiedene Aufrufe für die statischen Helfer verwenden, werden bei jeder Konstruktion mit regulären Ausdrücken die 5000 Einträge gecrawlt, um festzustellen, ob sie zuvor zwischengespeichert wurden. Um die Prüfung herum befindet sich eine Sperre , sodass die Prüfung die Parallelität verringern und eine Thread-Blockierung einführen kann.

Die Anzahl ist ziemlich niedrig eingestellt, um sich vor solchen Fällen zu schützen. In einigen Fällen haben Sie jedoch möglicherweise keine andere Wahl, als sie zu erhöhen.

Meine starke Empfehlung wäre, die Option niemalsRegexOptions.Compiled an einen statischen Helfer weiterzugeben .

Beispielsweise:

\\ WARNING: bad code
Regex.IsMatch("10000", @"\\d+", RegexOptions.Compiled)

Der Grund dafür ist, dass Sie einen Fehlschlag im LRU-Cache riskieren, der eine super teure Kompilierung auslöst . Darüber hinaus haben Sie keine Ahnung, was die Bibliotheken tun, von denen Sie abhängig sind. Daher können Sie die bestmögliche Größe des Caches kaum steuern oder vorhersagen .

Siehe auch: BCL-Teamblog


Hinweis : Dies ist relevant für .NET 2.0 und .NET 4.0. Es gibt einige erwartete Änderungen in 4.5, die dazu führen können, dass dies überarbeitet wird.

Sam Safran
quelle
11
Gute Antwort. Für meine eigenen Zwecke verwende ich häufig CompiledWebsite-Code, in dem ich tatsächlich ein statisches (anwendungsweites) RegexObjekt speichere . Das muss also Regexnur einmal erstellt werden, wenn IIS die Anwendung startet, und wird dann tausende Male wiederverwendet. Dies funktioniert gut, solange die Anwendung nicht häufig neu gestartet wird.
Steve Wortham
W00! Diese Informationen haben mir geholfen, meinen Prozess von 8-13 Stunden auf ~ 30 Minuten zu beschleunigen. Danke dir!
Robert Christ
3
Tolle Antwort Sam, kannst du aktualisieren, was sich in Version> 4.5 geändert hat? (Ich weiß, dass Sie vor
einiger Zeit
@gdoronissupportingMonica In NET 5.0 wurden einige Leistungsverbesserungen bei Regex vorgenommen. Ich habe dafür einen Blog-Beitrag gesehen. Sie können es hier
Kapozade
42

Dieser Eintrag im BCL Team Blog gibt einen schönen Überblick: " Regular Expression Performance ".

Kurz gesagt, es gibt drei Arten von Regex (jede wird schneller ausgeführt als die vorherige):

  1. interpretiert

    schnell im laufenden Betrieb zu erstellen, langsam auszuführen

  2. kompiliert (die, nach der Sie zu fragen scheinen)

    langsamer im laufenden Betrieb zu erstellen, schnell auszuführen (gut für die Ausführung in Schleifen)

  3. vorkompiliert

    Erstellen Sie zur Kompilierungszeit Ihrer App (keine Laufzeiterstellungsstrafe) und lassen Sie sie schnell ausführen

Wenn Sie also beabsichtigen, den regulären Ausdruck nur einmal oder in einem nicht leistungskritischen Abschnitt Ihrer App auszuführen (dh Überprüfung der Benutzereingaben), ist Option 1 in Ordnung.

Wenn Sie den regulären Ausdruck in einer Schleife ausführen möchten (dh zeilenweises Parsen der Datei), sollten Sie Option 2 wählen.

Wenn Sie viele reguläre Ausdrücke haben, die sich für Ihre App nie ändern und intensiv genutzt werden, können Sie Option 3 wählen.

Tomalak
quelle
1
Nummer 3 könnte durch eine benutzerdefinierte Roslyn leicht gemacht werdenCompileModule . Verdammt, ich muss mir die neue Plattform genauer ansehen.
Christian Gollhardt
9

Es ist zu beachten, dass die Leistung regulärer Ausdrücke seit .NET 2.0 durch einen MRU-Cache nicht kompilierter regulärer Ausdrücke verbessert wurde. Der Regex-Bibliothekscode interpretiert nicht mehr jedes Mal denselben nicht kompilierten regulären Ausdruck neu.

So gibt es möglicherweise eine größere Leistung Strafe mit einem kompilierten und on the fly regulären Ausdruck. Zusätzlich zu langsameren Ladezeiten verwendet das System auch mehr Speicher, um den regulären Ausdruck für Opcodes zu kompilieren.

Im Wesentlichen lautet der aktuelle Rat, entweder keinen regulären Ausdruck zu kompilieren oder diese vorab in einer separaten Assembly zu kompilieren.

Ref: BCL Team Blog Regular Expression Performance [David Gutierrez]

Robert Paulson
quelle
0

Ich hoffe, der folgende Code hilft Ihnen, das Konzept der re.compile-Funktionen zu verstehen

import re

x="""101 COM    Computers
205 MAT   Mathematics
189 ENG   English
222 SCI Science
333 TA  Tamil
5555 KA  Kannada
6666  TL  Telugu
777777 FR French
"""

#compile reg expression / successfully compiled regex can be used in any regex 
#functions    
find_subject_code=re.compile("\d+",re.M)
#using compiled regex in regex function way - 1
out=find_subject_code.findall(x)
print(out)
#using compiled regex in regex function way - 2
out=re.findall(find_numbers,x)
print(out)

#few more eg:
#find subject name
find_subjectnames=re.compile("(\w+$)",re.M) 
out=find_subjectnames.findall(x)
print(out)


#find subject SHORT name
find_subject_short_names=re.compile("[A-Z]{2,3}",re.M) 
out=find_subject_short_names.findall(x)
print(out)
Daniel Muthupandi
quelle
Vielen Dank für Ihre Antwort, aber Ihr Code ist in der Python- Sprache. Die Frage betraf die Option RegexOptions.Compiled von Microsoft .NET Framework . Sie können das [ .net ] -Tag unter der Frage sehen.
Stomy
Oh ja! Danke stomy
Daniel Muthupandi