Question

Ceci est juste une question pour satisfaire ma curiosité. Mais pour moi, c’est intéressant.

J'ai écrit ce petit repère simple. Il appelle plusieurs milliers de fois l’exécution de Regexp dans un ordre aléatoire:

En principe, j'utilise le même modèle mais de différentes manières.

  1. Votre façon habituelle sans RegexOptions . À partir de .NET 2.0, ceux-ci ne sont pas mis en cache. Mais devrait être "mis en cache" car il est maintenu dans une étendue plutôt globale et non réinitialisé.

  2. Avec RegexOptions.Compiled

  3. Avec un appel au statique Regex.Match (motif, entrée) qui est mis en cache dans .NET 2.0

Voici le code:

static List<string> Strings = new List<string>();        
static string pattern = ".*_([0-9]+)\\.([^\\.])
    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
quot;; 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()); }

Chaque fois que je l'appelle, le résultat est similaire à:

    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

Donc là vous l'avez. Pas beaucoup, mais environ 7-8% de différence.

Ce n’est pas le seul mystère. Je ne peux pas expliquer pourquoi la première méthode serait beaucoup plus lente, car elle n'est jamais réévaluée mais conservée dans une variable statique globale.

À propos, il s’agit de .Net 3.5 et Mono 2.2 qui se comportent exactement de la même manière. Sous Windows.

Alors, des idées, pourquoi la variante compilée serait même en retard?

EDIT1:

Après avoir corrigé le code, les résultats se présentent comme suit:

<*>

Ce qui obsolète à peu près toutes les autres questions.

Merci pour les réponses.

Était-ce utile?

La solution

Dans la version Regex.Match, vous recherchez l’entrée dans le motif. Essayez d’échanger les paramètres.

var m3 = Regex.Match(pattern, item); // Wrong
var m3 = Regex.Match(item, pattern); // Correct

Autres conseils

j'ai remarqué similaire comportement. Je me suis aussi demandé pourquoi la version compilée serait plus lente, mais j'ai remarqué qu'au-delà d'un certain nombre d'appels, la version compilée est plus rapide. Alors j’ai creusé dans Reflector un peu, et je l’ai remarqué pour un Regex compilé, il y a encore une petite configuration effectuée au premier appel (en particulier, création d'une instance du objet RegexRunner ).

Lors de mon test, j'ai constaté que si je déplaçais à la fois le constructeur et un appel initial à la regex en dehors du début du minuteur, la regex compilée gagnait quel que soit le nombre d'itérations que j'avais exécutées.

Incidemment, la mise en cache effectuée par la structure lors de l’utilisation de méthodes statiques Regex est une optimisation nécessaire uniquement lors de l’utilisation de méthodes statiques Regex . En effet, chaque appel à une méthode statique Regex crée un nouvel objet Regex . Dans le constructeur de la classe Regex , il doit analyser le motif. La mise en cache permet aux appels ultérieurs de méthodes Regex statiques de réutiliser le RegexTree analysé depuis le premier appel, évitant ainsi l’analyse.

Lorsque vous utilisez des méthodes d'instance sur un seul objet Regex , le problème ne se pose pas. L'analyse n'est encore effectuée qu'une fois (lorsque vous créez l'objet). De plus, vous évitez d’exécuter tout le code du constructeur, ainsi que l’allocation de tas (et le ramassage ultérieur).

Martin Brown remarqué que vous a inversé les arguments de votre appel statique Regex (bonne réponse, Martin). Je pense que vous constaterez que si vous corrigez cela, l'instance regex (non compilée) battra les appels statiques à chaque fois. Vous devriez également constater que, compte tenu de mes conclusions ci-dessus, l'instance compilée vaincra également l'instance non compilée.

MAIS : vous devriez vraiment lire Le message de Jeff Atwood sur les expressions rationnelles compilées avant d'appliquer aveuglément cette option à toutes les expressions rationnelles que vous créez.

Si vous faites constamment correspondre la même chaîne en utilisant le même modèle, cela peut expliquer pourquoi une version en cache est légèrement plus rapide qu'une version compilée.

Ceci provient de la documentation;

https://msdn.microsoft.com /en-us/library/gg578045(v=vs.110).aspx

  

lorsqu'une méthode expression régulière statique est appelée et que la méthode régulière   expression introuvable dans le cache, le moteur des expressions régulières   convertit l'expression régulière en un ensemble de codes d'opération et de magasins   dans la mémoire cache . Il convertit ensuite ces codes d'opération en MSIL afin   que le compilateur JIT peut les exécuter. régulier interprété   les expressions réduisent le temps de démarrage au détriment du temps d'exécution .   De ce fait, ils sont mieux utilisés lorsque l'expression régulière est   utilisé dans un petit nombre d'appels de méthode , ou si le nombre exact de   les appels aux méthodes d’expression régulière sont inconnus mais devraient être   petit. Au fur et à mesure que le nombre d'appels de méthode augmente, le gain de performances   du temps de démarrage réduit est dépassé par l'exécution plus lente   vitesse.

     

Contrairement aux expressions rationnelles interprétées, compilées régulières   les expressions augmentent le temps de démarrage mais exécutent individuellement   méthodes de correspondance de modèle plus rapidement . En conséquence, le bénéfice de performance   qui résulte de la compilation de l'expression régulière augmente en   proportion du nombre de méthodes d'expression régulières appelées.

  

Pour résumer, nous vous recommandons d’utiliser des expressions rationnelles interprétées lorsque vous appelez des méthodes d’expression régulière avec un élément spécifique.   expression régulière relativement peu fréquente.

     

Vous devez utiliser les expressions régulières compilées lorsque vous appelez des informations régulières.   méthodes d'expression avec une expression régulière spécifique relativement   fréquemment.

Comment détecter?

  

Le seuil exact auquel les vitesses d'exécution plus lentes de   interprétées expressions régulières l'emportent sur les gains de leur réduction   temps de démarrage, ou le seuil auquel les temps de démarrage plus lents de   les expressions régulières compilées l'emportent sur les gains de leur rapidité   les vitesses d'exécution, est difficile à déterminer. Cela dépend d'une variété   de facteurs, y compris la complexité de l'expression régulière et la   données spécifiques qu'il traite. Pour déterminer si interprété ou   Les expressions régulières compilées offrent les meilleures performances pour votre   scénario d’application particulier, vous pouvez utiliser la classe Stopwatch pour   comparer leurs temps d'exécution .

Expressions régulières compilées:

  

Nous vous recommandons de compiler des expressions régulières dans un assemblage dans   les situations suivantes:

     
      
  1. Si vous êtes un développeur de composants qui souhaite   créer une bibliothèque d'expressions régulières réutilisables.
  2.   
  3. Si vous vous attendez à   Les méthodes de correspondance des modèles de votre expression régulière à appeler un   nombre indéterminé de fois - de une ou deux fois à   des milliers ou des dizaines de milliers de fois. Contrairement à compilé ou   expressions régulières interprétées, expressions régulières compilées   séparer les assemblages offrent des performances cohérentes indépendamment de   du nombre d'appels de méthode.
  4.   
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top