3
votes

Performances des mots RegEx: \ w vs [a-zA-Z0-9_]

J'aimerais connaître la liste des caractères que \ w transmet, est-ce juste [a-zA-Z0-9_] ou y a-t-il plus de caractères qu'il pourrait couvrir?

Je pose cette question, car sur la base de ceci , \ d est différent avec [0-9] et est moins efficace < / a>.

\ w vs [a-zA-Z0-9_] : lequel pourrait être le plus rapide à grande échelle?


8 commentaires

voulez connaître la différence de performance entre 2 morceaux de code, comparez-les


démarrer la minuterie, faire des boucles 10k fois, arrêter la minuterie, comparer pour chaque, exécuter chaque test X nombre de cycles. Exemples de php.net/manual/en/function.microtime.php montrer comment configurer la minuterie


Prenez la bibliothèque BenchmarkDotNet github.com/dotnet/BenchmarkDotNet , écrivez des tests et comparez aakinshin.net/posts/stephen-toub-benchmarks-part1


@tim n'utilise pas de minuterie (chronomètre) pour les tests de performance


@Backs que suggérez-vous en php?


@tim github.com/phpbench/phpbench


il utilise le microtime, comme je l'ai suggéré ci-dessus, alors ne voyez pas votre objection


Si votre question porte sur la façon de tester les performances de X, posez-la.


3 Réponses :



3
votes

Cette réponse est basée sur Perl mais tous les outils balisés devraient être très similaires dans ce qui suit.

La classe de caractères \ w (pour un caractère "mot") suit les spécifications Unicode pour propriétés de caractère d'un "mot". Cela inclut tellement de choses et de complexité qu'il est difficile de spécifier les catégories de propriétés incluses. Voir « Caractères Word » dans perlrecharclass et ce message par exemple. Voir perlunicode et perluniprops pour l'arrière-plan.

En bref, c'est bien au-delà des 63 caractères ascii, à moins que / a (ou / aa ) modificateur ou des paramètres régionaux sont utilisés.

Cependant, la question concerne spécifiquement les performances. À ce stade, il faut s'attendre à ce que différents outils aient des comportements différents, et peut-être beaucoup, car cela dépend de l'implémentation des regex. Le reste de cet article est spécifique à Perl.

On peut s'attendre à ce qu'un ensemble plus petit soit plus rapide à vérifier, ou on peut s'attendre à ce que des constructions comme \ w viennent avec des optimisations . Au lieu de deviner, mesurons. Ce qui suit est un benchmark grossier visant des résultats raisonnables, en laissant de côté quelques nuances.

        Rate char word
char 72820/s   -- -19%
word 89863/s  23%   --

Une chaîne est assemblée en utilisant [a-zA-Z0-9 _] qui sont mélangés puis répétés 100 fois. Cette chaîne entière est mise en correspondance, caractère par caractère sous / g , par \ w et par [a-zA-Z0-9_] . Il s'agit donc d'une seule expression régulière dans chaque cas et celles-ci sont comparées.

Le résultat

$str = join '', qw(! / \ { } ^ % @) x 1_000;

Les chiffres ci-dessus vont jusqu'à 2% dans les deux cas dans diverses exécutions dans mes tests. Donc pas de différence.

Note: J'ai essayé avec des caractères non-ascii ajoutés à la chaîne de test, sans différence perceptible.

Remarque: l'expression régulière avec / g accumule les correspondances (6300) caractères après caractères, mais en une seule exécution du moteur. L'autre option consiste à rechercher une seule correspondance à plusieurs reprises. Ce ne sont pas les mêmes mais quoi qu'il en soit, les deux exposeront une différence de performances entre \ w et [a-zA-Z0-9_] si elle est considérable.

Veuillez le chronométrer pour vous-même, avec une chaîne et des motifs mieux adaptés à votre situation.


Le benchmark ci-dessus était censé être une mesure basique et approximative. Cependant, il manque particulièrement les correspondances négatives (échouantes), dans lesquelles le moteur devrait parcourir toutes les possibilités pour les modèles testés.

Je teste cela en invoquant les routines de référence ci-dessus sur la chaîne cible a été modifiée en

      Rate char word
char 583/s   --  -1%
word 587/s   1%   --

qui ne correspondra pas sous \ w et [a-zA-Z0-9 _] . Le résultat

use warnings;
use strict;
use feature 'say';

use List::Util qw(shuffle);
use Benchmark qw(cmpthese);

my $run_for = shift // 3;  # seconds to run benchmark for

my $str = join '', (shuffle 'a'..'z', 'A'..'Z', 0..9, '_') x 100;

sub word_class {
    my $str = shift;
    my @m_1 = $str =~ /\w/g;
    return \@m_1;
}

sub char_class {
    my $str = shift;
    my @m_2 = $str =~ /[a-zA-Z0-9_]/g;
    return \@m_2;
}


cmpthese(-$run_for, {
    word => sub { my $res = word_class ($str) },
    char => sub { my $res = char_class ($str) },
});

C'est une surprise pour moi, c'est le moins qu'on puisse dire. L'ensemble \ w est tellement plus grand (voir la réponse ikegami) que cela doit impliquer qu'il y a des optimisations lourdes (ou "magiques") en cours.

Ceci renforce ma conclusion générale: les performances de celles-ci sont assez proches en général, alors utilisez simplement ce qui est plus approprié en termes de codage; Ou chronométrez-le dans votre cas d'utilisation spécifique .


14 commentaires

C'est une référence plutôt médiocre. Il ne teste qu'un seul type de données, il ne vérifie que les correspondances positives et il ne vérifie pas vraiment les différences entre les deux choses évaluées car elles sont perdues dans le bruit du temps nécessaire pour appeler un sous-marin et démarrer le moteur de regex .


Re " [La différence est] perdue dans le bruit du temps nécessaire pour appeler un sous et démarrer le moteur regex ", je suppose que c'est significatif. Cela montre à quel point l'optimisation de cette question est peu efficace.


@ikegami Re " plutôt pauvre " - Je ne suis absolument pas d'accord. Ce n'était pas censé être exhaustif mais plutôt une prise approximative raisonnable; il est entièrement documenté - je déclare exactement quoi / comment est testé. La conclusion ("ça n'a pas d'importance") est raisonnable. (2) " ne teste qu'un seul type de données " - J'ai essayé avec des données non-ascii et cela n'a fait aucune différence; élaborer sur cela aurait été contraire à sa nature de " prise rought " (3) " ne vérifie que les correspondances positives " - il vérifie à travers une chaîne de près de 10K mélangée ascii ; il y a beaucoup de ratés. Encore une fois, c'est une prise difficile. (4) " perdu dans le bruit " - donc peu importe


@ikegami Pour mémoire: j'apprécie votre réponse détaillée (excellente comme toujours), et apprécie absolument vos commentaires / critiques sur celui-ci. Je ne suis simplement pas d'accord avec la qualification «pauvre»; Je pense que c'est utile tel quel.


@ikegami Une chose que je ne comprends pas cependant, avec " perdu dans le bruit du temps nécessaire pour appeler un sous-marin et démarrer le moteur regex " (1) il faut les exécuter pour le benchmark (? ) ... voulez-vous dire que le benchmark avec expression (plutôt que sous) est beaucoup plus rapide? (2) Il parcourt tous les caractères de la chaîne avec / g - cela ne redémarre pas le moteur? Et n'est-ce pas une manière raisonnable de tester plus d'une correspondance?


Re " Ce n'était pas censé être exhaustif ", Pour autant que vous le sachiez, il est un million de fois plus lent à échouer. Vous devez tester les deux. Il ne s’agit pas d’être exhaustif; il s'agit de ne pas avoir la moindre idée du type de données dont dispose l'OP et de ne pas documenter vos hypothèses massives. Selon votre logique (tester uniquement les correspondances), Perl est un moteur d'expression régulière extrêmement lent. Bien qu'en fait, il fonctionne plutôt bien car de nombreuses correspondances échouées de vitesse d'optimisation. C'est pourquoi il est important de tester.


Re " - il vérifie à travers une chaîne de près de 10K mélangée ascii; ", euh, non. Chaque caractère de la chaîne correspond.


Selon votre logique (tester uniquement les correspondances), Perl est un moteur d'expression régulière extrêmement lent. Bien qu'en fait, il fonctionne plutôt bien car de nombreuses correspondances échouées de vitesse d'optimisation


Re " Je ne suis pas d'accord avec la qualification" pauvre "", Eh bien, votre code ne montre même pas que l'un est en fait 10% plus rapide que l'autre ...


@ikegami " le code ne montre même pas que l'un est en fait 10% plus rapide que l'autre " --- euh, eh bien ... non? Les deux \ w et [...] correspondent à travers une longue chaîne avec les mêmes taux; pourquoi dites-vous que l'on est "en fait 10% plus rapide"? [J'ai terminé le test négatif, et ils ne sont pas les mêmes ... le \ w est 23% plus rapide ?? Vérification...]


Comme je l'ai dit, votre code compare l'appel d'un sous + démarrage du moteur regex + correspondance d'un caractère en utilisant une méthode vs sub + appel d'un démarrage du moteur regex + correspondance d'un caractère en utilisant l'autre méthode. Il y a tellement de frais généraux que vous enterrez la différence de bruit.


@ikegami " euh, non. Chaque caractère de la chaîne correspond. (quelques commentaires ci-dessus) --- eh bien, cela correspond mais il doit trouver la correspondance pour un caractère en main, dans tout ce qu'il considère pour ce motif (il y aurait donc beaucoup plus de possibilités pour \ w etc). C'est un moyen de vérifier. (Bien sûr, un mathc échec est un moyen beaucoup plus sûr.)


@ikegami " Comme je l'ai dit, ... " - OK, mais pourquoi dites-vous " correspondant à un caractère " --- il parcourt toute la chaîne avec < code> / g , dans ce sous-appel, et si je ne me trompe pas, il ne redémarre pas non plus le moteur pour chaque correspondance.


@ikegami " Les benchmarks sont trivialement faciles à mal faire " - oui, je les gère depuis des décennies maintenant ... c'est pourquoi je continue à en discuter, car je pense que celui-ci est raisonnable pas défectueux / faux / ...) et sinon j'aimerais savoir pourquoi. En parlant de cela - un test négatif (échec) étant plus rapide pour \ w me dit simplement qu'il y a trop de «magie» (optimisations) en coulisse.



5
votes

[ Cette réponse est spécifique à Perl. Les informations contenues peuvent ne pas s'appliquer à PCRE ou au moteur utilisé par les autres langues marquées. ]

/ \ w / aa (l'équivalent réel de / [a- zA-Z0-9 _] / ) est généralement plus rapide, mais pas toujours. Cela dit, la différence est si minime (moins de 1 nanoseconde par chèque) qu'elle ne devrait pas être un problème. Pour le mettre en contexte, il faut beaucoup, beaucoup plus de temps pour appeler un sous-marin ou démarrer le moteur de regex.

Ce qui suit couvre cela en détail.


Tout d'abord , \ w n'est pas la même chose que [a-zA-Z0-9_] par défaut. \ w correspond à tous les Ponctuation alphabétique, numérique, de marque et de connecteur Point de code Unicode. Il y en a 119 821! [1] Déterminer quel est le code non équivalent le plus rapide n'a aucun sens.

Cependant, utiliser \ w avec / aa garantit que \ w ne correspond qu'à [a-zA-Z0-9_] . C'est donc ce que nous allons utiliser pour nos benchmarks. (En fait, nous utiliserons les deux.)

(Notez que chaque test effectue 10 millions de vérifications, donc un taux de 10,0 / s signifie en fait 10,0 millions de vérifications par seconde.)


use strict;
use warnings;
use feature qw( say );

use Benchmarks qw( cmpthese );

my %pos_tests = (
   '(?u:\\w)'     => '/^\\w*\\z/u',
   '(?aa:\\w)'    => '/^\\w*\\z/aa',
   '[a-zA-Z0-9_]' => '/^[a-zA-Z0-9_]*\\z/',
);

my %neg_tests = (
   '(?u:\\w)'     => '/\\w/u',
   '(?aa:\\w)'    => '/\\w/aa',
   '[a-zA-Z0-9_]' => '/[a-zA-Z0-9_]/',
);

$_ = sprintf( 'use strict; use warnings; our $s; for (1..1000) { $s =~ %s }', $_)
   for
      values(%pos_tests),
      values(%neg_tests);

local our $s;

say "ASCII-only positive match";
$s = "J" x 10_000;
cmpthese(-3, \%pos_tests);

say "";

say "ASCII-only negative match";
$s = "!" x 10_000;
cmpthese(-3, \%neg_tests);

say "";

say "Non-ASCII positive match";
$s = "\N{U+0100}" x 10_000;
cmpthese(-3, \%pos_tests);

say "";

say "Non-ASCII negative match";
$s = "\N{U+2660}" x 10_000;
cmpthese(-3, \%neg_tests);

Lors de la recherche d'une correspondance en caractères ASCII, \ w ASCII uniquement et Unicode \ w battent tous les deux la classe explicite. p>

/ \ w / aa est (1 / 39,1 - 1 / 60,9) / 10 000 000 = 0 000 000 000 916 s plus rapide sur ma machine


Non-ASCII negative match
               Rate      (?u:\w) [a-zA-Z0-9_]     (?aa:\w)
(?u:\w)      2.66/s           --          -9%         -71%
[a-zA-Z0-9_] 2.91/s          10%           --         -68%
(?aa:\w)     9.09/s         242%         212%           --
  • Je suis surpris qu'il y ait une différence entre / \ w / aa et / [a-zA-Z0-9 _] / .
  • Dans certaines situations, / \ w / aa est plus rapide; dans d'autres, / [a-zA-Z0-9 _] / .
  • La différence entre / \ w / aa et / [a-zA-Z0-9 _] / est très minime (moins de 1 nanoseconde). li >
  • La différence est si minime que vous ne devriez pas vous en préoccuper.
  • Même la différence entre / \ w / aa et / \ w / u est assez faible, bien que ce dernier corresponde à 4 ordres de grandeur de plus de caractères que le premier.
  • / li>

ASCII-only positive match
               Rate [a-zA-Z0-9_]      (?u:\w)     (?aa:\w)
[a-zA-Z0-9_] 39.1/s           --         -26%         -36%
(?u:\w)      52.9/s          35%           --         -13%
(?aa:\w)     60.9/s          56%          15%           --

  1. Unicode version 11.


2 commentaires

Je ne pense pas que cela fera une différence, mais / a est suffisant pour rendre \ w exactement équivalent à [a-zA-Z0-9 _] < / code> tant que vous n'utilisez pas / i .


@Grinnz, je suis resté simple. J'ai dit que "utiliser \ w avec / aa garantit que \ w ne correspond qu'à [a-zA-Z0-9 _] < / code> ", une revendication qui ne peut pas être faite pour / a sans sortir du sujet.