Perl中的“goatse运算符”或=()=
习语导致在列表上下文中计算表达式。
一个例子是:
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";
当我解释使用时,会发生这种情况:
$str =~ /\d/g
匹配所有数字。 g
开关和列表上下文生成这些匹配的列表。让它成为“List Producer”示例,在Perl中可以有很多东西。=()=
导致对空列表的分配,因此所有实际匹配都会复制到空列表中。=()=
的引用计数变为零。然后Perl删除列表元素的副本。 有关效率的问题是:
这个简单的列表很有用,但是如果列表是成千上万的匹配怎么办?使用此方法,您将生成每个匹配的完整副本,然后将其删除以计算它们。
答案 0 :(得分: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 :(得分: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
示例在标量(布尔)上下文中具有正则表达式,因此正则表达式引擎只需返回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% --
我猜测sub为什么更快是因为子程序调用被优化为传递列表而不复制它们(作为别名传递)。
答案 2 :(得分:3)
如果需要在列表上下文中运行某些内容,则必须在列表上下文中运行它。在某些情况下,就像你提出的那种情况一样,你可能可以用另一种方法解决它,但在大多数情况下你不会。
然而,在您进行基准测试之前,最重要的问题是“它是否重要?”。在您进行基准测试之前的配置文件,只有在您遇到实际问题时才会担心这些问题。 :)
如果你正在寻找最高效率,Perl的水平有点过高。 :)