Comment fonctionne RegexOptions.Compiled?

169

Que se passe-t-il dans les coulisses lorsque vous marquez une expression régulière comme une expression à compiler? En quoi cela compare-t-il / est-il différent d'une expression régulière mise en cache?

À l'aide de ces informations, comment déterminez-vous quand le coût du calcul est négligeable par rapport à l'augmentation des performances?

Bob
la source
bonne ressource sur les meilleures pratiques Regex: docs.microsoft.com/en-us/dotnet/standard/base-types
CAD bloke

Réponses:

302

RegexOptions.Compiledindique au moteur d'expression régulière de compiler l'expression d'expression régulière en IL à l'aide de la génération de code léger ( LCG ). Cette compilation se produit lors de la construction de l'objet et le ralentit fortement . À leur tour, les correspondances utilisant l'expression régulière sont plus rapides.

Si vous ne spécifiez pas cet indicateur, votre expression régulière est considérée comme "interprétée".

Prenons cet exemple:

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

    }
}

Il effectue 4 tests sur 3 expressions régulières différentes. Tout d'abord, il teste une seule correspondance unique (compilée vs non compilée). Ensuite, il teste les correspondances répétées qui réutilisent la même expression régulière.

Les résultats sur ma machine (compilés en version, pas de débogueur attaché)

1000 matchs simples (construire Regex, Match et disposer)

Type | Plateforme | Numéro trivial | Vérification simple des e-mails | Vérification des e-mails ext
-------------------------------------------------- ----------------------------
Interprété | x86 | 4 ms | 26 ms | 31 ms
Interprété | x64 | 5 ms | 29 ms | 35 ms
Compilé | x86 | 913 ms | 3775 ms | 4487 ms
Compilé | x64 | 3300 ms | 21985 ms | 22793 ms

1,000,000 correspondances - réutilisation de l'objet Regex

Type | Plateforme | Numéro trivial | Vérification simple des e-mails | Vérification des e-mails ext
-------------------------------------------------- ----------------------------
Interprété | x86 | 422 ms | 461 ms | 2122 ms
Interprété | x64 | 436 ms | 463 ms | 2167 ms
Compilé | x86 | 279 ms | 166 ms | 1268 ms
Compilé | x64 | 281 ms | 176 ms | 1180 ms

Ces résultats montrent que les expressions régulières compilées peuvent être jusqu'à 60% plus rapides dans les cas où vous réutilisez l' Regexobjet. Cependant, dans certains cas, la construction peut être plus lente de plus de 3 ordres de grandeur .

Il montre également que la version x64 de .NET peut être 5 à 6 fois plus lente en ce qui concerne la compilation d'expressions régulières.


La recommandation serait d' utiliser la version compilée dans les cas où soit

  1. Vous ne vous souciez pas du coût d'initialisation des objets et avez besoin d'une amélioration supplémentaire des performances. (notez que nous parlons ici de fractions de milliseconde)
  2. Vous vous souciez un peu du coût d'initialisation, mais réutilisez l'objet Regex tellement de fois qu'il le compensera pendant le cycle de vie de votre application.

Spanner en préparation, le cache Regex

Le moteur d'expressions régulières contient un cache LRU qui contient les 15 dernières expressions régulières qui ont été testées à l'aide des méthodes statiques de la Regexclasse.

Par exemple: Regex.Replace, Regex.Matchetc .. tous utiliser le cache Regex.

La taille du cache peut être augmentée en définissant Regex.CacheSize. Il accepte les changements de taille à tout moment au cours du cycle de vie de votre application.

Les nouvelles expressions régulières ne sont mises en cache que par les helpers statiques de la classe Regex. Si vous construisez vos objets, le cache est vérifié (pour une réutilisation et un transfert), cependant, l'expression régulière que vous construisez n'est pas ajoutée au cache .

Ce cache est un cache LRU trivial , il est implémenté à l'aide d'une simple double liste chaînée. Si vous l'augmentez à 5000 et utilisez 5000 appels différents sur les helpers statiques, chaque construction d'expression régulière analysera les 5000 entrées pour voir si elle a déjà été mise en cache. Il y a un verrou autour de la vérification, de sorte que la vérification peut diminuer le parallélisme et introduire un blocage de thread.

Le nombre est fixé assez bas pour vous protéger de cas comme celui-ci, bien que dans certains cas, vous n'ayez pas d'autre choix que de l'augmenter.

Ma forte recommandation serait de ne jamais passer l' RegexOptions.Compiledoption à un assistant statique.

Par exemple:

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

La raison en est que vous risquez fortement de manquer le cache LRU, ce qui déclenchera une compilation très coûteuse . De plus, vous n'avez aucune idée de ce que font les bibliothèques dont vous dépendez, vous avez donc peu de capacité à contrôler ou à prédire la meilleure taille possible du cache.

Voir aussi: Blog de l'équipe BCL


Remarque : ceci est pertinent pour .NET 2.0 et .NET 4.0. Certains changements attendus dans la version 4.5 pourraient entraîner une révision de ce dernier.

Sam Safran
la source
11
Très bonne réponse. Pour mes propres besoins, j'utilise souvent Compileddans le code de site Web où je stocke en fait un objet statique (à l'échelle de l'application) Regex. Donc, le Regexseul doit être construit une fois lorsque IIS démarre l'application, puis est réutilisé des milliers de fois. Cela fonctionne bien tant que l'application ne redémarre pas fréquemment.
Steve Wortham
W00! Ces informations m'ont aidé à accélérer mon processus de 8 à 13 heures à environ 30 minutes. Je vous remercie!
Robert Christ
3
Bonne réponse Sam, pouvez-vous mettre à jour ce qui a changé dans la version> 4.5? (Je sais que vous avez changé votre tapis il y a quelque temps ...)
gdoron soutient Monica le
@gdoronissupportingMonica Il y a eu quelques améliorations de performances Regex sur NET 5.0. J'ai vu un article de blog à ce sujet. Vous pouvez le vérifier ici
kapozade
42

Cette entrée dans le blog de l'équipe BCL donne un bel aperçu: " Performances des expressions régulières ".

En bref, il existe trois types de regex (chacun s'exécutant plus rapidement que le précédent):

  1. interprété

    rapide à créer à la volée, lent à exécuter

  2. compilé (celui que vous semblez demander)

    plus lent à créer à la volée, rapide à exécuter (bon pour une exécution en boucle)

  3. pré-compilé

    créer au moment de la compilation de votre application (pas de pénalité de création au moment de l'exécution), rapide à exécuter

Donc, si vous avez l'intention d'exécuter l'expression régulière une seule fois, ou dans une section non critique pour les performances de votre application (c'est-à-dire la validation des entrées utilisateur), vous êtes d'accord avec l'option 1.

Si vous avez l'intention d'exécuter l'expression régulière en boucle (c'est-à-dire une analyse ligne par ligne du fichier), vous devriez choisir l'option 2.

Si vous avez de nombreuses expressions régulières qui ne changeront jamais pour votre application et sont utilisées intensément, vous pouvez choisir l'option 3.

Tomalak
la source
1
Le numéro 3 pourrait être rendu facile grâce à un roslyn personnaliséCompileModule . Merde, j'ai besoin d'examiner plus en profondeur la nouvelle plate-forme.
Christian Gollhardt
9

Il est à noter que les performances des expressions régulières depuis .NET 2.0 ont été améliorées avec un cache MRU d'expressions régulières non compilées. Le code de la bibliothèque Regex ne réinterprète plus la même expression régulière non compilée à chaque fois.

Il y a donc potentiellement une plus grande pénalité de performances avec une expression régulière compilée et à la volée. En plus des temps de chargement plus lents, le système utilise également plus de mémoire pour compiler l'expression régulière en opcodes.

Essentiellement, le conseil actuel est soit de ne pas compiler une expression régulière, soit de les compiler à l'avance dans un assembly distinct.

Réf: BCL Team Blog Performance d'expression régulière [David Gutierrez]

Robert Paulson
la source
0

J'espère que le code ci-dessous vous aidera à comprendre le concept des fonctions 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)
Daniel Muthupandi
la source
Merci pour votre réponse mais votre code est en langage Python . La question était sur le Microsoft .NET cadre RegexOptions.Compiled option. Vous pouvez voir la balise [ .net ] attachée sous la question.
stomie
ohh oui! Merci stomy
Daniel Muthupandi