2010-10-21 26 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