2010-10-21 1 views
25

L'opérateur "goatse" ou l'idiome =()= dans Perl provoque l'évaluation d'une expression dans un contexte de liste.L'Opérateur Secret de Perl Goatse est-il efficace?

Un exemple est:

my $str = "5 and 4 and a 3 and 2 1 BLAST OFF!!!"; 
my $count =()= $str =~ /\d/g; # 5 matches... 
print "There are $count numbers in your countdown...\n\n"; 

Comme j'interprete l'utilisation, c'est ce qui se passe:

  1. $str =~ /\d/g correspond à tous les chiffres. Le commutateur g et le contexte de liste produisent une liste de ces correspondances. Soit ceci l'exemple "List Producer", et en Perl cela pourrait être beaucoup de choses. Le =()= provoque une affectation à une liste vide, de sorte que toutes les correspondances réelles sont copiées dans une liste vide.
  2. L'affectation dans un contexte scalaire à $ count de la liste produite en 2. donne le nombre de la liste ou le résultat de 5.
  3. Le nombre de référence de la liste vide =()= va à zéro après l'affectation scalaire. La copie des éléments de la liste est ensuite supprimée par Perl.

Les questions sur l'efficacité sont les suivants:

  1. Ai-je tort que je suis ce Parsing?
  2. Si vous avez un producteur de liste et que tout ce qui vous intéresse est le nombre, y a-t-il un moyen plus efficace de le faire?

Cela fonctionne très bien avec cette liste triviale, mais si la liste était des centaines de milliers de correspondances? Avec cette méthode, vous produisez une copie complète de chaque match, puis vous le supprimez simplement pour les compter.

+12

Il n'est pas aussi efficace que de concevoir une puce personnalisée pour implémenter votre programme. – jrockway

+2

Pourquoi le vote à la baisse? N'est-ce pas une question juste sur le compromis entre l'efficacité et l'idiome? – dawg

+16

Désolé, je n'ai pas pu résister à être l'auteur de la balise '' [goatse] '. : D – Ether

Répondre

23

Perl 5 est intelligent à propos de la copie de listes. Il copie seulement autant d'éléments que sur le côté gauche. Cela fonctionne parce que l'assignation de liste dans un contexte scalaire donne le nombre d'éléments sur le côté droit. Ainsi, n éléments seront créés par la regex, mais ils ne seront pas copiés et mis au rebut, juste mis au rebut. Vous pouvez voir la différence que fait la copie supplémentaire dans le cas naïf de l'indice de référence ci-dessous. En ce qui concerne l'efficacité, une solution itérative est souvent plus facile pour la mémoire et l'utilisation du CPU, mais cela doit être comparé à la concision de l'opérateur secret de chèvre. Voici les résultats de l'analyse comparative des différentes solutions:

naive: 10 
iterative: 10 
goatse: 10 

for 0 items: 
       Rate iterative goatse  naive 
iterative 4365983/s  --  -7%  -12% 
goatse 4711803/s  8%  --  -5% 
naive  4962920/s  14%  5%  -- 

for 1 items: 
       Rate  naive goatse iterative 
naive  749594/s  --  -32%  -69% 
goatse 1103081/s  47%  --  -55% 
iterative 2457599/s  228%  123%  -- 

for 10 items: 
       Rate  naive goatse iterative 
naive  85418/s  --  -33%  -82% 
goatse 127999/s  50%  --  -74% 
iterative 486652/s  470%  280%  -- 

for 100 items: 
      Rate  naive goatse iterative 
naive  9309/s  --  -31%  -83% 
goatse 13524/s  45%  --  -76% 
iterative 55854/s  500%  313%  -- 

for 1000 items: 
      Rate  naive goatse iterative 
naive  1018/s  --  -31%  -82% 
goatse 1478/s  45%  --  -75% 
iterative 5802/s  470%  293%  -- 

for 10000 items: 
      Rate  naive goatse iterative 
naive  101/s  --  -31%  -82% 
goatse 146/s  45%  --  -75% 
iterative 575/s  470%  293%  -- 

Voici le code qui a généré:

#!/usr/bin/perl 

use strict; 
use warnings; 

use Benchmark; 

my $s = "a" x 10; 

my %subs = (
    naive => sub { 
     my @matches = $s =~ /a/g; 
     return scalar @matches; 
    }, 
    goatse => sub { 
     my $count =()= $s =~ /a/g; 
     return $count; 
    }, 
    iterative => sub { 
     my $count = 0; 
     $count++ while $s =~ /a/g; 
     return $count; 
    }, 
); 

for my $sub (keys %subs) { 
    print "$sub: @{[$subs{$sub}()]}\n"; 
} 

for my $n (0, 1, 10, 100, 1_000, 10_000) { 
    $s = "a" x $n; 
    print "\nfor $n items:\n"; 
    Benchmark::cmpthese -1, \%subs; 
} 
+1

+1: Merci. J'apprécie vraiment comment vous avez abordé la logique de ceci et vous avez capturé ce que j'imaginais être le cas: Plus vous en avez, meilleure est l'itération. Mais si Perl est "intelligent" sur la copie du nombre qui est nécessaire sur le côté gauche, avec '=() =' ne serait-ce pas tous? – dawg

+0

Non, il n'y a pas de cibles sur le côté gauche, donc aucune donnée n'est copiée (mais l'expression régulière doit encore générer celles du côté droit). –

+0

Convenu que si vous avez quelque chose comme '($ i, $ j, $ k) =/a/g;' va copier 3 éléments même s'il y a 10 correspondances. Mais si vous avez '() =/a/g;' Perl est-il assez intelligent pour voir qu'il y a zéro affectations à copier? – dawg

13

Dans votre exemple particulier, un point de repère est utile:

my $str = "5 and 4 and a 3 and 2 1 BLAST OFF!!!"; 

use Benchmark 'cmpthese'; 

cmpthese -2 => { 
    goatse => sub { 
     my $count =()= $str =~ /\d/g; 
     $count == 5 or die 
    }, 
    while => sub { 
     my $count; 
     $count++ while $str =~ /\d/g; 
     $count == 5 or die 
    }, 
}; 

qui retours:

  Rate goatse while 
goatse 285288/s  -- -57% 
while 661659/s 132%  -- 

Le $str =~ /\d/g dans le contexte de liste capture la sous-chaîne correspondante même si elle n'est pas nécessaire. L'exemple while a la regex dans un contexte scalaire (booléen), donc le moteur de regex doit juste retourner vrai ou faux, et non les correspondances réelles.

Et en général, si vous avez une liste fonction de production et ne se soucient que du nombre d'articles, écrire une courte fonction count est plus rapide:

sub make_list {map {$_**2} 0 .. 1000} 

sub count {scalar @_} 

use Benchmark 'cmpthese'; 

cmpthese -2 => { 
    goatse => sub {my $count =()= make_list; $count == 1001 or die}, 
    count => sub {my $count = count make_list; $count == 1001 or die}, 
}; 

qui donne:

  Rate goatse count 
goatse 3889/s  -- -26% 
count 5276/s 36%  -- 

Mon devinez pourquoi le sous-marin est plus rapide parce que les appels de sous-programme sont optimisés pour passer des listes sans les copier (transmis comme alias).

+2

+1: Les Benchamarks sont toujours meilleurs que les suppositions oisives. Merci! – dawg

3

Si vous devez exécuter quelque chose dans un contexte de liste, vous devez l'exécuter dans un contexte de liste. Dans certains cas, comme celui que vous présentez, vous pourriez peut-être contourner cette technique avec une autre technique, mais dans la plupart des cas, vous ne le pourrez pas. Avant de vous référencer, cependant, la question la plus importante est "Est-ce important?". Profil avant de vous comparer, et ne vous inquiétez de ce genre de choses que lorsque vous êtes à court de problèmes à résoudre. :)

Si vous recherchez le summum de l'efficacité, Perl est un peu trop haut niveau. :)

+1

"Est-ce important?" Est une question juste. Cela compte pour * moi * pour deux raisons: 1) Je suis curieux! Si j'utilise 1 idiome contre un autre, j'aime penser à l'arrière de ma tête pourquoi je fais ça. 2) Si j'utilise un raccourci, j'aime comprendre les rouages. Je peux aussi bien avoir l'habitude de taper l'idiome de '$ count ++ alors que $ s = ~/a/g' car je peux' $ count =() = $ s = ~/a/g; '. Si l'un a tendance à être beaucoup plus rapide que l'autre, j'aurai tendance à le favoriser sans dire que l'autre est «mauvais». – dawg

+0

êtes-vous en train de créer un tag wiki pour cet "opérateur"? http://stackoverflow.com/tags/goatse/info – Ether

+0

Je ne suis pas à la hauteur. –

Questions connexes