【问题标题】:How fast is Perl's smartmatch operator when searching for a scalar in an array?在数组中搜索标量时,Perl 的智能匹配运算符有多快?
【发布时间】:2016-09-14 01:30:58
【问题描述】:

我想在一个不变的数组中反复搜索值。

到目前为止,我一直在这样做:我将值放在哈希中(所以我有一个数组和一个内容基本相同的哈希),然后我使用 exists 搜索哈希。

我不喜欢有两个不同的变量(数组和散列)都存储​​相同的东西;但是,散列的搜索速度要快得多。

我发现 Perl 5.10 中有一个~~(智能匹配)运算符。在数组中搜索标量的效率如何?

【问题讨论】:

  • 我相信“智能匹配”仍然需要每次都搜索整个数组,这意味着每次搜索的时间大约为 O(N)。而搜索哈希是 O(​​1)。
  • Paul:嗯,这就是我的问题的重点......智能匹配每次都会通过数组,还是......更智能? :)
  • 智能匹配不必搜索整个数组。有人可能会实现这样的智能匹配,但 Perl 5.12 没有。但是,即使在最好的情况下,这仍然不能使它在速度方面比哈希更好。
  • Paul Tomblin:哈希中的“搜索”不是 O(1)。它是 O(log n)。
  • @alexandr:如果“搜索”你(和保罗)的意思是“查找”,那么按照所有实际标准,它是 O(1)。根据实现对哈希冲突的处理,在特殊情况下可能是 O(log(n)) 或 O(n)。据我所知,Perl 有各种技巧可以防止这种情况发生,所以让我重复一遍:出于所有实际目的,哈希查找是 O(1)。

标签: perl smartmatch


【解决方案1】:

如果要在数组中搜索单个标量,可以使用List::Utilfirst 子例程。它一知道答案就停下来。我不认为这会比散列查找更快如果你已经有了散列,但是当你考虑创建散列并将它放在内存中时,你可能更方便只搜索你已经拥有的数组。

至于 smart-match 算子的聪明,如果你想看看它有多聪明,那就测试一下吧。 :)

您至少要检查三个案例。最坏的情况是你想找到的每个元素都在最后。最好的情况是您要查找的每个元素都位于开头。可能的情况是您要查找的元素平均位于中间。

现在,在我开始这个基准测试之前,我希望如果智能匹配可以短路(它可以;它记录在 perlsyn 中),那么尽管数组大小,最佳情况时间将保持不变,而其他的变得越来越糟。如果不能短路,每次都要扫描整个阵列,时间上应该没有区别,因为每个案例的工作量都是一样的。

这是一个基准:

#!perl
use 5.12.2;
use strict;
use warnings;

use Benchmark qw(cmpthese);

my @hits = qw(A B C);
my @base = qw(one two three four five six) x ( $ARGV[0] || 1 );

my @at_end       = ( @base, @hits );
my @at_beginning = ( @hits, @base );

my @in_middle = @base;
splice @in_middle, int( @in_middle / 2 ), 0, @hits;

my @random = @base;
foreach my $item ( @hits ) {
    my $index = int rand @random;
    splice @random, $index, 0, $item;
    }

sub count {
    my( $hits, $candidates ) = @_;

    my $count;
    foreach ( @$hits ) { when( $candidates ) { $count++ } }
    $count;
    }

cmpthese(-5, {
    hits_beginning => sub { my $count = count( \@hits, \@at_beginning ) },
    hits_end       => sub { my $count = count( \@hits, \@at_end ) },
    hits_middle    => sub { my $count = count( \@hits, \@in_middle ) },
    hits_random    => sub { my $count = count( \@hits, \@random ) },
    control        => sub { my $count = count( [], [] ) },
  }
);

这是各个部分的作用。请注意,这是两个轴上的对数图,因此下降线的斜率并不像看起来那么接近:

所以,看起来智能匹配运算符有点聪明,但这并不能真正帮助您,因为您可能仍然需要扫描整个数组。您可能事先不知道在哪里可以找到您的元素。我希望散列的性能与最佳情况智能匹配相同,即使您必须为此放弃一些内存。


好的,智能乘以 2 的智能匹配很棒,但真正的问题是“我应该使用它吗?”。另一种方法是哈希查找,我一直在烦恼我没有考虑过这种情况。

与任何基准测试一样,在实际测试之前,我会先考虑结果可能是什么。我希望如果我已经有了哈希值,那么查找一个值会很快。那个案子没问题。我对我还没有哈希的情况更感兴趣。我可以多快使散列和查找成为键?我预计它的表现不会那么好,但它仍然比最坏情况下的智能匹配更好吗?

但是,在您查看基准之前,请记住,仅通过查看数字几乎没有足够的信息来说明您应该使用哪种技术。问题的上下文选择最好的技术,而不是最快的、无上下文的微基准。考虑几个可以选择不同技术的案例:

  • 您有一个要重复搜索的数组
  • 您总是会得到一个只需要搜索一次的新数组
  • 您会得到非常大的数组,但内存有限

现在,牢记这些,我将添加到我之前的程序中:

my %old_hash = map {$_,1} @in_middle; 

cmpthese(-5, {
    ...,
    new_hash       => sub { 
        my %h = map {$_,1} @in_middle; 
        my $count = 0;
        foreach ( @hits ) { $count++ if exists $h{$_} }
        $count;
        },
    old_hash       => sub { 
        my $count = 0;
        foreach ( @hits ) { $count++ if exists $old_hash{$_} }
        $count;
        },
    control_hash   => sub { 
        my $count = 0;
        foreach ( @hits ) { $count++ }
        $count;
        },
    }
);

这是情节。颜色有点难以区分。最低行是您必须在任何时候想要搜索它时创建散列的情况。可以说是很可怜了。最高的两行(绿色)是散列控制(实际上没有散列)和现有散列查找。这是一个日志/日志图;这两种情况甚至比智能匹配控制(只调用一个子程序)还要快。

还有一些其他的事情需要注意。 “随机”案例的行有点不同。这是可以理解的,因为每个基准测试(因此,每个数组规模运行一次)将命中元素随机放置在候选数组中。有些运行将它们放早一点,有些放慢一点,但是由于我每次运行整个程序时只创建一次@random 数组,因此它们会移动一点。这意味着生产线上的颠簸并不重要。如果我尝试了所有位置并取平均值,我希望“随机”线与“中间”线相同。

现在,看看这些结果,我想说智能匹配在最坏的情况下比哈希查找在最坏的情况下要快得多。那讲得通。要创建散列,我必须访问数组的每个元素并生成散列,这需要大量复制。智能匹配没有复制。

这里还有一个我不会研究的案例。哈希何时变得比智能匹配更好?也就是说,什么时候创建哈希的开销在重复搜索中分散得足够多,以至于哈希是更好的选择?

【讨论】:

  • 我刚刚使用了 Numbers(来自 iWork)。我不认为它们那么好,但这是我得心应手的。
  • 出色的答案!谢谢,布赖恩。
【解决方案2】:

对于少量潜在匹配来说速度很快,但不比散列快。哈希确实是测试集合成员的正确工具。由于哈希访问是 O(log n) 并且数组上的 smartmatch 仍然是 O(n) 线性扫描(尽管是短路,不像 grep),允许匹配中的值数量更多,smartmatch 变得相对更差。

基准代码(匹配 3 个值):

#!perl
use 5.12.0;
use Benchmark qw(cmpthese);

my @hits = qw(one two three);
my @candidates = qw(one two three four five six); # 50% hit rate
my %hash;
@hash{@hits} = ();

sub count_hits_hash {
  my $count = 0;
  for (@_) {
    $count++ if exists $hash{$_};
  }
  $count;
}

sub count_hits_smartmatch {
  my $count = 0;
  for (@_) {
    $count++ when @hits;
  }
  $count;
}

say count_hits_hash(@candidates);
say count_hits_smartmatch(@candidates);

cmpthese(-5, {
    hash => sub { count_hits_hash((@candidates) x 1000) },
    smartmatch => sub { count_hits_smartmatch((@candidates) x 1000) },
  }
);

基准测试结果:

             Rate smartmatch       hash
smartmatch  404/s         --       -65%
hash       1144/s       183%         --

【讨论】:

  • 这是一个小的候选数组。我敢打赌,如果数组有 25 个以上的项目,那么会有更大的差异。
  • 候选人的大小对相对表现没有真正的影响。您是指点击量的大小吗?
  • 我重新调整了基准以尝试使用不同的候选数组大小并使用候选数组中命中的不同位置。它有很大的不同。
【解决方案3】:

“智能匹配”中的“智能”与搜索无关。这是关于根据上下文在正确的时间做正确的事情。

循环遍历数组或索引到哈希是否更快的问题是您必须进行基准测试的问题,但通常,它必须是一个非常小的数组才能比索引更快地浏览成一个哈希。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-02-24
    • 2017-02-07
    • 2013-01-12
    • 2013-05-31
    • 1970-01-01
    • 2021-11-06
    • 1970-01-01
    相关资源
    最近更新 更多