使用散列和散列引用的Perl速度比较

时间:2014-05-26 03:23:00

标签: perl hash

我很想比较是否更好地使用哈希或哈希引用,哈希引用,因为我理解只是一个指向哈希本身的指针,所以我认为应该没有速度差异。

我做了一个基本的基准测试,我发现哈希引用比使用哈希直接平均速度慢20-27%。

以下是我使用的基本基准代码:

use Benchmark qw(:all);

cmpthese(10_000_000, {
    hash            =>  sub { my %hash = (); },
    hashref =>  sub { my $hahref = {}; }
});

cmpthese(10_000_000, {
    hash            =>  sub {
                                    my %hash;
                                    $hash{fname}="first name";
                                    $hash{mname}="middle name";
                                    $hash{lname}="last name";
                                },

    hashref =>  sub {
                                    my $hahref;
                                    $hahref->{fname}="first name"; 
                                    $hahref->{mname}="middle name";
                                    $hahref->{lname}="last name"; 
                                },

    hashrefs    =>  sub {
                                    my $hahref;
                                    $$hahref{fname}="first name"; 
                                    $$hahref{mname}="middle name";
                                    $$hahref{lname}="last name"; 
                                },
});

这是笔记本电脑戴尔,Windows 8,核心i7,16MB RAM的基准测试结果:

             Rate hashref    hash
hashref 5045409/s      --    -17%
hash    6045949/s     20%      --

             Rate hashrefs  hashref     hash
hashrefs 615764/s       --      -2%     -21%
hashref  625978/s       2%       --     -19%
hash     775134/s      26%      24%       --

Output completed (1 min 6 sec consumed)

我的问题是,如果我的基准测试是正确的并且散列引用速度太慢,为什么大多数模块(如DBI)都使用散列引用来返回结果。此外,大多数模块接受散列引用而不是参数哈希值,并且还返回散列引用而不是哈希值。

4 个答案:

答案 0 :(得分:5)

哈希从访问元素的速度更快; hashrefs作为参数传递给函数的速度更快,或者作为函数的结果返回。如果您考虑一下,这是有道理的:

  • hashref基本上是指向散列的指针,因此当Perl看到$href->{xyz}时,它需要跟随指针找到散列,然后在散列中找到元素xyz。当Perl看到$hash{xyz}时,它不需要执行该初始指针跟随位;它可以立即找到元素xyz

  • 哈希不能直接传递给潜艇;他们需要被扁平化为一系列标量。如果哈希有四个键和四个值,那么将它传递给子意味着将八个标量列表传递给该函数。在函数内部,您可能会有类似my %args = @_的内容,将这八个标量复制到 new 哈希中。有很多工作要做。传递hashref只是传递单个标量的问题,因此速度更快。

大多数情况下,这是微优化,您应该选择最适合您的程序的数据结构。然而,对于那些你真的需要尽可能地提高速度的场合,它可以拥有两全其美......

让我们假设您有一个需要接受哈希值的函数(或者也可能是hashref;您尚未决定)并且需要添加一些键。以下是您最初的两个选项:

sub add_hash {
    my %hash = @_;
    return $hash{foo} + $hash{bar} + $hash{baz};
}

sub add_hashref {
    my ($href) = @_;                                    # faster than add_hash
    return $href->{foo} + $href->{bar} + $href->{baz};  # slower than add_hash
}

现在让我们拉出Data::Alias。这个模块允许我们创建一个词法变量,作为另一个变量的别名。特别是,我们可以创建一个词法哈希变量,它就像hashref所指向的哈希的别名。

use Data::Alias;

sub add_hashref_2 {
    my ($href) = @_;                               # faster than add_hash
    alias my %hash = %$href;                       # ... magic happens ...
    return $hash{foo} + $hash{bar} + $hash{baz};   # faster than add_hashref
}

或者更好的是:

use Data::Alias;

sub add_hashref_3 {
    alias my %hash = %{ $_[0] };
    return $hash{foo} + $hash{bar} + $hash{baz};
}

...这避免了初始列表分配。

我强调这是 micro - 优化。通常有更好的方法来加速你的代码 - 记忆,激进的算法更改,在XS中重写选定的热代码路径等等。但是有一些(非常有限的)场合,这种魔法可以帮助。

答案 1 :(得分:4)

您的基准测试有问题。

您的hashref示例不仅使用散列,还为每次迭代创建它。哈希示例经过优化,可以始终重用相同的哈希值。

如果您修改第二个基准测试以强制简单哈希版本始终创建新哈希,则hashref版本会变得更快:

cmpthese(10_000_000, {
    hash => sub {
        my %hash;
        $hash{fname}="first name";
        $hash{mname}="middle name";
        $hash{lname}="last name";
        return \%hash;          
    },
    hashref => sub {
        my $hahref;
        $hahref->{fname}="first name"; 
        $hahref->{mname}="middle name";
        $hahref->{lname}="last name"; 
        return $hahref;         
    },
} );

但真正的观点是停止尝试微观优化;以有意义的方式编写代码,并且只有在证明存在问题的情况下,才能看到实际上不良的代码以进行优化。

答案 2 :(得分:3)

当然,通过引用访问哈希元素比直接访问哈希元素要慢。额外的工作需要额外的时间。

但需要多长时间?根据你的测试,

( 1 / (5045409/s) - 1/(6045949/s) ) / 3 derefs
= 0.000,000,011 s/deref
= 11 ns/deref

这不是你应该担心的事情!

  

如果我的基准测试是正确的并且哈希引用是如此慢

您的基准测试并不表明它们很慢。

  

为什么像DBI这样的大多数模块都使用散列引用来返回结果。

与什么相反? sub可以返回的唯一内容是标量列表。它无法返回哈希值。 fetch_hashref可以返回一个键值对列表,您可以从中创建一个哈希值,但如果它已经在子域中构建了哈希,那么它将比使用引用慢得多。

答案 3 :(得分:1)

据我了解(这可能会产生误导),返回引用与实际数据结构的最大优势在于子例程之外的赋值 - 而不是访问结构的性能。返回引用不会复制赋值中的内存中的数据。

我希望第二个例子可能更慢。

my $data = getData();

sub getData {
    return { a => '1' };
}

VS

my %data = getData();

sub getData {
    return my %hash = ( a => '1' );
}