Perl Goatse 'Secret Operator' 高效吗?

Posted

技术标签:

【中文标题】Perl Goatse \'Secret Operator\' 高效吗?【英文标题】:Is the Perl Goatse 'Secret Operator' efficient?Perl Goatse 'Secret Operator' 高效吗? 【发布时间】:2011-04-28 20:26:54 【问题描述】:

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 开关和列表上下文会生成这些匹配项的列表。让这成为“列表生产者”示例,在 Perl 中这可能是很多东西。 =()= 导致分配给一个空列表,因此所有实际匹配项都被复制到一个空列表中。 在标量上下文中对 2 中生成的列表的 $count 赋值。给出列表的计数或 5 的结果。 在标量赋值后,空列表=()= 的引用计数变为零。然后列表元素的副本被 Perl 删除。

关于效率的问题如下:

    我的解析方式错了吗? 如果您有一些 List Producer 并且您只对计数感兴趣,是否有更有效的方法来执行此操作?

这个简单的列表很好用,但如果列表有数十万个匹配项怎么办?使用这种方法,您将生成每个匹配项的完整副本,然后将其删除以计算它们。

【问题讨论】:

它不如设计一个定制的微芯片来实现你的程序那么高效。 为什么投反对票?这不是一个关于效率和习语之间权衡的公平问题吗? 抱歉,我忍不住成为[goatse] 的标签创建者。 :D 列表没有引用计数;它们总是短暂的 @Ether:你要写标签 wiki 条目吗? 【参考方案1】:

Perl 5 在复制列表方面很聪明。它只复制与左侧一样多的项目。它之所以有效,是因为标量上下文中的列表分配会产生右侧的项目数。因此,n 项目将由正则表达式创建,但它们不会被复制和丢弃,只是被丢弃。您可以在下面的基准测试中看到额外副本在幼稚情况下的不同。

至于效率,迭代解决方案在内存和 CPU 使用方面通常更容易,但这必须与 goatse 秘密运算符的简洁性进行权衡。以下是对各种解决方案进行基准测试的结果:

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:谢谢。我真的很欣赏你如何处理这个逻辑并且你抓住了我想象的情况:你拥有的越多,迭代越好。但是,如果 Perl 能够“聪明”地复制左侧所需的数字,那么 =()= 不就是全部吗? 不,左边没有目标,所以没有数据被复制(但正则表达式仍然需要生成右边的目标)。 同意如果您有类似($i, $j, $k)=/a/g; 的内容,即使有 10 个匹配项,也会复制 3 个项目。但是如果你有()=/a/g;,那么 Perl 是否足够聪明,可以看到零赋值副本 0? @drewk 是的,就是这么聪明。 请原谅我公然插入我自己的软件,但对于基准测试,请查看我的Dumbbench 工具或Benchmark::Dumb 兼容性包装器,它与 Benchmark.pm 几乎相同,只是更好。文档试图解释原因。【参考方案2】:

在您的特定示例中,基准测试很有用:

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 更快的猜测是因为子例程调用经过优化,可以在不复制列表的情况下传递列表(作为别名传递)。

【讨论】:

+1:基准总是比空闲假设好。谢谢!【参考方案3】:

如果您需要在列表上下文中运行某些内容,则必须在列表上下文中运行它。在某些情况下,就像您介绍的那样,您也许可以使用另一种技术来解决它,但在大多数情况下您不会。

然而,在您进行基准测试之前,最重要的问题是“这是否重要?”。在进行基准测试之前进行分析,并且只有在没有真正需要解决的问题时才担心这些事情。 :)

但是,如果您正在寻找极致的效率,Perl 的水平有点太高了。 :)

【讨论】:

“这是否重要”是一个公平的问题。 很重要,原因有两个:1)我很好奇!如果我使用一个成语与另一个成语,我喜欢在脑后思考我为什么这样做。 2)如果我使用捷径,我喜欢了解它的具体细节。我可以像$count =()= $s =~ /a/g; 一样轻松地养成输入$count++ while $s =~/a/g 的习惯。如果一个往往比另一个快很多,我会倾向于支持它而不说另一个是“错误的”。 你准备为这个“操作员”创建一个标签维基吗? ***.com/tags/goatse/info

以上是关于Perl Goatse 'Secret Operator' 高效吗?的主要内容,如果未能解决你的问题,请参考以下文章

Opera 浏览器各版本下载地址

Opera官网打不开 下载Opera最新版本的实际地址

Opera扩展清单文件

k8s 配置存储之 Configmap & secret

k8s 配置存储之 Configmap & secret

找软件? FF OPERA