191 votes

Comment fonctionne RegexOptions.Compiled ?

Que se passe-t-il en coulisses lorsque vous marquez une expression régulière comme devant être compilée ? Comment cela se compare-t-il/se différencie-t-il d'une expression régulière mise en cache ?

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

0 votes

Bonne ressource sur les meilleures pratiques en matière de Regex : docs.microsoft.com/fr/us/dotnet/standard/base-types/

334voto

Sam Saffron Points 56236

RegexOptions.Compiled indique au moteur d'expressions régulières de compiler l'expression régulière en IL à l'aide de la génération de code légère ( LCG ). Cette compilation se fait pendant la construction de l'objet et lourdement le ralentit. En revanche, les correspondances utilisant l'expression régulière sont plus rapides.

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

Prenez 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[] { "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]);
        });

    }
}

Il effectue 4 tests sur 3 expressions régulières différentes. Il teste d'abord une simple une correspondance unique (compilée ou non). 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é en release, pas de débogueur attaché)

1000 correspondances simples (construction de Regex, Match and dispose)

Type        | Platform | Trivial Number | Simple Email Check | Ext Email Check
------------------------------------------------------------------------------
Interpreted | x86      |    4 ms        |    26 ms           |    31 ms
Interpreted | x64      |    5 ms        |    29 ms           |    35 ms
Compiled    | x86      |  913 ms        |  3775 ms           |  4487 ms
Compiled    | x64      | 3300 ms        | 21985 ms           | 22793 ms

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

Type        | Platform | Trivial Number | Simple Email Check | Ext Email Check
------------------------------------------------------------------------------
Interpreted | x86      |  422 ms        |   461 ms           |  2122 ms
Interpreted | x64      |  436 ms        |   463 ms           |  2167 ms
Compiled    | x86      |  279 ms        |   166 ms           |  1268 ms
Compiled    | x64      |  281 ms        |   176 ms           |  1180 ms

Ces résultats montrent que les expressions régulières compilées peuvent aller jusqu'à 60% plus rapide pour les cas où vous réutilisez le Regex objet. Cependant dans certains cas, peut être supérieur 3 ordres de grandeur plus lente à construire.

Il montre également que le version x64 de .NET peut être 5 à 6 fois plus lent lorsqu'il s'agit de la compilation d'expressions régulières.


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

  1. Vous ne vous souciez pas du coût d'initialisation de l'objet et vous avez besoin d'une augmentation supplémentaire des performances. (Notez que nous parlons ici de fractions de millisecondes).
  2. Vous vous souciez un peu du coût d'initialisation, mais vous réutilisez l'objet Regex tellement de fois qu'il compensera ce coût au cours du cycle de vie de votre application.

Le cache Regex, un élément clé de la stratégie de l'UE

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 sur le site Web de l'entreprise. Regex classe.

Par exemple : Regex.Replace , Regex.Match etc. utilisent tous 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 sont uniquement mises en cache par les aides statiques sur la classe Regex. Si vous construisez vos objets, le cache est vérifié (pour la réutilisation et le bumped), cependant, l'expression régulière que vous construisez est ne sont pas ajoutés au cache .

Ce cache est un trivial Cache LRU, il est implémenté en utilisant une simple liste doublement liée. Si vous l'augmentez à 5000, et utilisez 5000 appels différents sur les aides statiques, chaque construction d'expression régulière va parcourir les 5000 entrées pour voir si elle a déjà été mise en cache. Il existe un serrure autour de la vérification, de sorte que la vérification peut réduire le parallélisme et introduire le blocage des threads.

Ce chiffre est fixé assez bas pour vous protéger de ce genre de cas, bien que dans certains cas vous n'ayez pas d'autre choix que de l'augmenter.

Mon forte recommandation serait jamais passer le RegexOptions.Compiled à une aide statique.

Par exemple :

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

La raison en est que vous risquez fortement de rater le cache LRU, ce qui déclenchera un message d'erreur. très cher compiler. De plus, vous n'avez aucune idée de ce que font les bibliothèques dont vous dépendez, et vous n'avez donc que peu de possibilités de contrôler ou de prédire l'évolution de la le meilleur possible la taille du cache.

Voir aussi : Blog de l'équipe du BCL


Note : 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 document.

12 votes

Excellente réponse. Pour mes propres besoins, j'utilise souvent Compiled dans le code du site web où je stocke un fichier statique (pour toute l'application). Regex objet. Ainsi, le Regex ne doit être construit qu'une seule fois lorsque IIS démarre l'application, et est ensuite réutilisé des milliers de fois. Cela fonctionne bien tant que l'application ne redémarre pas fréquemment.

2 votes

W00 ! Cette information m'a aidé à accélérer mon processus de 8-13 heures à ~30 minutes. Merci !

3 votes

Excellente réponse Sam, pouvez-vous nous dire ce qui a changé dans la version > 4.5 ? (Je sais que tu as changé ta pile il y a quelque temps...)

51voto

Tomalak Points 150423

Cette entrée dans le blog de l'équipe BCL donne un bon 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ées

    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 l'exécution en boucle)

  3. pré-compilé

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

Donc, si vous avez l'intention de n'exécuter l'expression rationnelle qu'une seule fois, ou dans une section de votre application qui n'est pas critique en termes de performances (par exemple, la validation des entrées utilisateur), l'option 1 vous convient parfaitement.

Si vous avez l'intention d'exécuter la regex en boucle (c'est-à-dire en analysant le fichier ligne par ligne), vous devez choisir l'option 2.

Si vous avez beaucoup de regex qui ne changeront jamais pour votre application et qui sont utilisés intensivement, vous pouvez choisir l'option 3.

1 votes

Le numéro 3 pourrait être facilité par un Roslyn personnalisé. CompileModule . Bon sang, il faut que je regarde de plus près la nouvelle plateforme.

9voto

Robert Paulson Points 10792

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

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

Pour l'essentiel, le conseil actuel est soit de ne pas compiler d'expression régulière, soit de les compiler à l'avance dans un assemblage séparé.

Ref : BCL Team Blog Performance des expressions régulières [David Gutierrez].

2voto

-2voto

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)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X