簡體   English   中英

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

[英]Perl speed comparison on using hash and hash reference

我很想比較是否更好地使用哈希或引用哈希,哈希引用,因為我理解只是一個指向哈希本身的指針,所以我認為應該沒有速度差異。

我做了一個基本的基准測試,我發現哈希引用比使用哈希直接平均速度慢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這樣的大多數模塊都使用散列引用來返回結果。 此外,大多數模塊接受散列引用而不是參數的散列,並且還返回散列引用而不是散列。

哈希更快地訪問元素; hashrefs作為參數傳遞給函數的速度更快,或者作為函數的結果返回。 如果你仔細想想,這是有道理的:

  • hashref基本上是指向散列的指針,因此當Perl看到$href->{xyz} ,它需要跟隨指針找到散列,然后在散列中找到元素xyz 當Perl看到$hash{xyz}它不需要執行該初始指針跟隨位; 它可以直接找到元素xyz

  • 哈希不能直接傳遞給潛艇; 他們需要被扁平化為一系列標量。 如果哈希有四個鍵和四個值,那么將它傳遞給子意味着將八個標量列表傳遞給該函數。 在函數內部,你可能會有像my %args = @_這樣的東西,它將這8個標量復制到一個新的散列中。 有很多工作要做。 傳遞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};
}

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

我強調這是微觀優化。 通常有更好的方法來加速你的代碼 - 記憶,激進的算法更改,在XS中重寫選定的熱代碼路徑等等。但是有一些(非常有限的)場合,這種魔法可以幫助。

您的基准測試有問題。

您的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;         
    },
} );

但真正的觀點是停止嘗試微觀優化; 以有意義的方式編寫代碼,並且只有在證明存在問題的情況下,才能看到實際上不良的代碼以進行優化。

當然,通過引用訪問哈希元素比直接訪問哈希元素要慢。 額外的工作需要額外的時間

但需要多長時間? 根據你的測試,

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

這不是你應該擔心的!

如果我的基准測試是正確的,並且散列引用是如此之慢

您的基准測試並未表明它們很慢。

為什么像DBI這樣的大多數模塊都使用hash refs來返回結果。

與什么相反? sub可以返回的唯一內容是標量列表。 它無法返回哈希值。 fetch_hashref可以返回一個鍵值對列表,您可以從中創建一個哈希值,但如果它已經在子fetch_hashref構建了哈希,那么它將比使用引用要慢得多。

據我所知(這可能會產生誤導),返回引用與實際數據結構的最大優點是在子例程之外的賦值 - 而不是訪問結構的性能。 返回引用不會復制賦值中的內存中的數據。

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

my $data = getData();

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

VS

my %data = getData();

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM