Come funziona RegexOptions.Compiled?


169

Cosa succede dietro le quinte quando si contrassegna un'espressione regolare come una da compilare? In che modo questo confronto / è diverso da un'espressione regolare memorizzata nella cache?

Utilizzando queste informazioni, come si determina quando il costo del calcolo è trascurabile rispetto all'aumento delle prestazioni?


buona risorsa sulle migliori pratiche Regex: docs.microsoft.com/en-us/dotnet/standard/base-types/…
CAD bloke

Risposte:


302

RegexOptions.Compiledindica al motore di espressione regolare di compilare l'espressione di espressione regolare in IL utilizzando la generazione di codice leggero ( LCG ). Questa compilation avviene durante la costruzione dell'oggetto e lo rallenta pesantemente . A loro volta, le partite che usano l'espressione regolare sono più veloci.

Se non si specifica questo flag, l'espressione regolare viene considerata "interpretata".

Prendi questo esempio:

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[] { "sam@sam.com", "sss@s", "sjg@ddd.com.au.au", "onelongemail@oneverylongemail.com" };

    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]);
        });

    }
}

Esegue 4 test su 3 diverse espressioni regolari. Innanzitutto verifica una singola partita una tantum (compilata vs non compilata). In secondo luogo, verifica le corrispondenze ripetute che riutilizzano la stessa espressione regolare.

I risultati sul mio computer (compilato in versione, nessun debugger allegato)

1000 partite singole (costruisci Regex, Match e dispose)

Digita | Piattaforma | Numero di prova | Controllo e-mail semplice | Ext Email Check
-------------------------------------------------- ----------------------------
Interpretato | x86 | 4 ms | 26 ms | 31 ms
Interpretato | x64 | 5 ms | 29 ms | 35 ms
Compilato | x86 | 913 ms | 3775 ms | 4487 ms
Compilato | x64 | 3300 ms | 21985 ms | 22793 ms

1.000.000 di corrispondenze: riutilizzo dell'oggetto Regex

Digita | Piattaforma | Numero di prova | Controllo e-mail semplice | Ext Email Check
-------------------------------------------------- ----------------------------
Interpretato | x86 | 422 ms | 461 ms | 2122 ms
Interpretato | x64 | 436 ms | 463 ms | 2167 ms
Compilato | x86 | 279 ms | 166 ms | 1268 ms
Compilato | x64 | 281 ms | 176 ms | 1180 ms

Questi risultati mostrano che le espressioni regolari compilate possono essere fino al 60% più veloci nei casi in cui riutilizzi l' Regexoggetto. Tuttavia, in alcuni casi, può essere più lento di costruire oltre 3 ordini di grandezza .

Mostra anche che la versione x64 di .NET può essere da 5 a 6 volte più lenta quando si tratta di compilare espressioni regolari.


La raccomandazione sarebbe quella di utilizzare la versione compilata nei casi in cui entrambi

  1. Non ti interessa il costo di inizializzazione degli oggetti e hai bisogno di aumentare le prestazioni extra. (nota che stiamo parlando di frazioni di millisecondi qui)
  2. Ti preoccupi un po 'del costo di inizializzazione, ma stai riutilizzando così tante volte l'oggetto Regex che lo compenserà durante il ciclo di vita dell'applicazione.

Spanner in lavorazione, la cache Regex

Il motore delle espressioni regolari contiene una cache LRU che contiene le ultime 15 espressioni regolari che sono state testate utilizzando i metodi statici sulla Regexclasse.

Ad esempio: Regex.Replace, Regex.Matchecc .. tutto l'uso della cache Regex.

La dimensione della cache può essere aumentata impostando Regex.CacheSize. Accetta modifiche di dimensioni in qualsiasi momento durante il ciclo di vita dell'applicazione.

Le nuove espressioni regolari vengono memorizzate nella cache solo dagli helper statici nella classe Regex. Se costruisci i tuoi oggetti, la cache viene controllata (per riutilizzarli e eliminarli), tuttavia, l'espressione regolare che costruisci non viene aggiunta alla cache .

Questa cache è una cache LRU banale , viene implementata utilizzando un semplice elenco a doppio collegamento. Se ti capita di aumentarlo a 5000 e utilizzare 5000 chiamate diverse sugli helper statici, ogni costruzione di espressioni regolari eseguirà la scansione delle 5000 voci per vedere se è stata precedentemente memorizzata nella cache. C'è un lucchetto attorno al controllo, quindi il controllo può ridurre il parallelismo e introdurre il blocco del thread.

Il numero è impostato piuttosto basso per proteggersi da casi come questo, anche se in alcuni casi potresti non avere altra scelta che aumentarlo.

La mia forte raccomandazione non sarebbe mai passare l' RegexOptions.Compiledopzione a un assistente statico.

Per esempio:

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

Il motivo è che stai rischiando pesantemente di perdere una cache LRU che attiverà una compilazione super costosa . Inoltre, non hai idea di cosa stiano facendo le librerie da cui dipendi, quindi hai poca capacità di controllare o prevedere la migliore dimensione possibile della cache.

Vedi anche: blog del team BCL


Nota : questo è rilevante per .NET 2.0 e .NET 4.0. Ci sono alcune modifiche previste in 4.5 che potrebbero causare la revisione di questo.


11
Bella risposta. Per i miei scopi spesso uso Compilednel codice del sito Web in cui sto effettivamente memorizzando un oggetto statico (a livello di applicazione) Regex. Pertanto, l' Regexunica volta che IIS viene avviata, è necessario crearla una sola volta, per poi riutilizzarla migliaia di volte. Funziona bene fino a quando l'applicazione non si riavvia frequentemente.
Steve Wortham,

W00! Queste informazioni mi hanno aiutato ad accelerare il mio processo da 8-13 ore, a ~ 30 minuti. Grazie!
Robert Christ,

3
Ottima risposta Sam, puoi aggiornarmi su cosa è cambiato nella versione> 4.5? (So ​​che hai cambiato il tuo stack qualche tempo fa ...)
Gdoron supporta Monica l'

@gdoronissupportingMonica Sono stati apportati alcuni miglioramenti alle prestazioni di Regex su NET 5.0. Ho visto un post sul blog per questo. Puoi dare un'occhiata qui
kapozade

42

Questa voce nel Blog del team BCL offre una bella panoramica: " Performance delle espressioni regolari ".

In breve, ci sono tre tipi di regex (ognuno eseguendo più velocemente del precedente):

  1. interpretato

    veloce da creare al volo, lento da eseguire

  2. compilato (quello a cui sembra chiedere)

    più lento da creare al volo, veloce da eseguire (buono per l'esecuzione in loop)

  3. pre-compilato

    crea al momento della compilazione della tua app (nessuna penalità per la creazione di runtime), veloce da eseguire

Quindi, se hai intenzione di eseguire regex solo una volta, o in una sezione non critica delle prestazioni della tua app (vale a dire la convalida dell'input dell'utente), stai bene con l'opzione 1.

Se si intende eseguire il regex in un ciclo (ovvero analisi riga per riga del file), è necessario utilizzare l'opzione 2.

Se hai molte regex che non cambieranno mai per la tua app e vengono utilizzate intensamente, potresti scegliere l'opzione 3.


1
Il numero 3 potrebbe essere reso semplice attraverso un roslyn personalizzatoCompileModule . Dannazione, devo dare uno sguardo più approfondito alla nuova piattaforma.
Christian Gollhardt,

9

Va notato che le prestazioni delle espressioni regolari da .NET 2.0 sono state migliorate con una cache MRU di espressioni regolari non compilate. Il codice della libreria Regex non reinterpreta più la stessa espressione regolare non compilata ogni volta.

Quindi c'è potenzialmente una penalità di prestazione maggiore con un'espressione regolare compilata e al volo. Oltre a tempi di caricamento più lenti, il sistema utilizza anche più memoria per compilare l'espressione regolare in codici operativi.

In sostanza, il consiglio attuale è di non compilare un'espressione regolare o di compilarli in anticipo in un assembly separato.

Rif: BCL Team Blog Espressione regolare performance [David Gutierrez]



0

Spero che il codice seguente ti aiuti a comprendere il concetto di funzioni re.compile

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)

Grazie per la risposta, ma il codice è in linguaggio Python . La domanda riguardava l' opzione RegexOptions.Compiled del framework Microsoft .NET . Puoi vedere il tag [ .net ] allegato sotto la domanda.
ventre

Oh sì! Grazie stomy
Daniel Muthupandi
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.