2010-10-21 73 views
25

Perl中的“goatse operator”或=()=成语会导致在列表上下文中评估表达式。Perl Goatse'Secret Operator'是否高效?

一个例子是:

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"; 

正如我interprete使用,这是发生了什么:

  1. $str =~ /\d/g所有的数字相匹配。 g开关和列表上下文产生这些匹配的列表。让它成为“List Producer”示例,在Perl中,这可能是很多事情。
  2. =()=导致对空列表的分配,因此所有实际匹配都被复制到空列表中。
  3. 在标量上下文中2生成的列表$数分配给该列表的计数或5
  4. 空列表的引用计数=()=标转让后变为零的结果。列表元素的副本然后由Perl删除。

对效率的问题是:

  1. 我错在我是怎么解析呢?
  2. 如果您有一些List Producer,并且您感兴趣的是count,是否有更高效的方法来执行此操作?

它可以很好地处理这个简单的列表,但是如果列表成千上万的匹配呢?使用这种方法,您将生成每场比赛的完整副本,然后删除它以便对它们进行计数。

+12

它不如设计自定义微芯片来实现您的程序效率。 – jrockway 2010-10-21 20:59:09

+2

为什么选择投票?这对于效率和成语之间的权衡是不是一个公平的问题? – dawg 2010-10-21 21:02:08

+16

对不起,我无法抗拒成为'[goatse]'的标签创作者。 :D – Ether 2010-10-21 21:48:14

回答

23

Perl 5对于复制列表非常明智。它只复制左侧的许多项目。它的工作原理是标量上下文中的列表赋值产生右侧的项目数量。所以,n项目将由正则表达式创建,但它们不会被复制和丢弃,只是被丢弃。您可以在下面的基准测试中看到额外副本在天真案例中的差异。

至于效率,迭代解决方案通常更容易处理内存和CPU使用,但必须权衡山羊秘密操作员的简洁性。以下是标杆的各种解决方案的结果:

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%  -- 

这里是产生它的代码:

#!/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:谢谢。我非常感谢你如何接近这个逻辑,并且捕获了我所想象的情况:你拥有的越多,迭代越好。但是如果Perl对于复制左边所需的数字是“聪明”的话,那么'=()='不就是全部吗? – dawg 2010-10-21 21:06:33

+0

不,左侧没有目标,所以没有数据被复制(但正则表达式仍然必须在右侧生成)。 – 2010-10-21 21:09:03

+0

同意,如果你有'($ i,$ j,$ k)=/a/g之类的东西,'即使有10个匹配项也会复制3个项目。但是,如果你有'()=/a/g;'是Perl足够聪明,看到有零分配复制0? – dawg 2010-10-21 21:24:30

13

在你的具体的例子,一个基准是有用的:

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 
    }, 
}; 

这退货:

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

列表上下文中的$str =~ /\d/g捕获匹配的子字符串,即使它不是必需的。 while示例在标量(boolean)上下文中具有正则表达式,因此正则表达式引擎只需返回true或false,而不是实际的匹配。

而在一般情况下,如果你有一个列表产生功能,只关心项目的数量,写一个短count功能更快:

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}, 
}; 

这给:

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

我猜测为什么子速度更快是因为子程序调用被优化为传递列表而不复制它们(作为别名传递)。

+2

+1:Benchamarks总是比闲置假设好。谢谢! – dawg 2010-10-21 21:07:04

3

如果你需要在列表上下文中运行某些东西,你必须在列表上下文中运行它。在某些情况下,如您所介绍的那种,您可以使用其他技术解决此问题,但在大多数情况下您不会。

然而,在您进行基准测试之前,最重要的问题是“它甚至重要吗?”。在您进行基准测试之前进行配置文件分析,只有在您遇到实际问题需要解决时才担心这些事情。 :)

如果你正在寻找最终的效率,但Perl的水平有点高。 :)

+1

“它甚至重要”是一个公平的问题。这对我来说很重要,原因有两个:1)我很好奇!如果我使用一个习语与另一个习语,我喜欢在脑后思考为什么我这样做。 2)如果我使用捷径,我想了解它的细节。我可以很容易习惯于输入'$ count ++的习语,而$ s =〜/ a/g',因为我可以'$ count =()= $ s =〜/ a/g;'。如果一个人比另一个人要快得多,我会倾向于赞成而不会说对方是“错误的”。 – dawg 2010-10-21 23:48:38

+0

你想为这个“运营商”创建一个标签wiki吗? http://stackoverflow.com/tags/goatse/info – Ether 2010-10-23 18:45:57

+0

我不胜任。 – 2010-10-23 22:14:07