2012-03-07 12 views
7

FORWARD注:請爲討論的緣故,讓我們忽略了一會兒,同樣的結束可以有追索權的實現,以Class::Accessor,甚至根本事實使用Moose(可能在考慮代碼可讀性和可維護性時具有更好的結果)。使用閉包修改Perl中開始上課塊

關於面向對象的Perl,書Programming Perl討論了使用閉包生成存取器方法的能力。例如,這是一個代碼有效片:

#!perl 

use v5.12; 
use warnings; 

# at run-time 
package Person1; 

my @attributes = qw/name age address/; 

for my $att (@attributes) 
{ 
    my $accessor = __PACKAGE__ . "::$att"; 

    no strict 'refs'; # allow symbolic refs to typeglob 

    *$accessor = sub { 
    my $self = shift; 
    $self->{$att} = shift if @_; 
    return $self->{$att}; 
    }; 
} 

sub new { bless {}, shift } 

package main; 

use Data::Dumper; 

my $dude = Person1->new; 
$dude->name('Lebowski'); 
say Dumper($dude); 

在上面的例子中,如果我沒有弄錯,類是由在運行時,在同一時間作爲類被創建及其訪問正在被實例化。這意味着在創建對象時會有速度懲罰。

現在考慮以下選擇:

#!perl 

use v5.12; 
use warnings; 

package Person2; 

BEGIN 
{ 
    for my $att (qw/name age address/) 
    { 
    my $accessor = __PACKAGE__ . "::$att"; 

    no strict 'refs'; # allow symbolic refs to typeglob 

    *$accessor = sub { 
     my $self = shift; 
     $self->{$att} = shift if @_; 
     return $self->{$att}; 
    }; 
    } 
} 

sub new { bless {}, shift } 

package main; 

use Data::Dumper; 

my $dude = Person2->new; 
$dude->name('Lebowski'); 
say Dumper($dude); 

在這個版本中,組成一個BEGIN塊(即,在編譯時)內進行,而且我相信,只要完成這個任務處理可能在程序的生命週期中,我在運行時對象實例化過程中節省了時間。

一個簡單Benchmark

# benchmark it! 
package main; 

use Benchmark qw/cmpthese/; 

cmpthese(-2, { 
    accessors_new => sub { Person1->new }, 
    accessors_begin => sub { Person2->new }, 
}); 

看似支持我的理論與這些結果:

    Rate accessors_begin accessors_new 
accessors_begin 853234/s    --    -9% 
accessors_new 937924/s    10%    -- 

假設我的推理一直是正確的,到目前爲止,

  • 什麼其他的好處/在比較這兩種策略時存在缺陷嗎?
  • 依靠BEGIN塊作爲進行這種類操作的有效方法是否是一個好主意?
  • 什麼時候不推薦?

回答

10

當我運行你的基準測試時,我得到了很大的震動,這可以解釋你的差異。對於任何10%或更少的差異,運行幾次以確保。

     Rate accessors_begin accessors_new 
accessors_begin 1865476/s    --    -4% 
accessors_new 1943339/s    4%    -- 

        Rate accessors_begin accessors_new 
accessors_begin 1978799/s    --    -1% 
accessors_new 2001062/s    1%    -- 

        Rate accessors_new accessors_begin 
accessors_new 1943339/s    --    -2% 
accessors_begin 1988089/s    2%    -- 

        Rate accessors_begin accessors_new 
accessors_begin 1796509/s    --    -8% 
accessors_new 1949296/s    9%    -- 

        Rate accessors_begin accessors_new 
accessors_begin 1916122/s    --    -3% 
accessors_new 1969595/s    3%    -- 

但是真的你所有的基準測試都是sub new { bless {}, shift }。將相同的事情與自己進行比較將會強調撲滅。生成訪問器的工作已經在加載代碼時完成,並且始終不會進行,BEGIN是否阻塞。

Perl沒有一個編譯時和運行時。相反,每個東西use d,require d或eval ed都會經歷它自己的編譯和運行時步驟。 use Some::Class導致Some/Class.pm經歷編譯和運行時執行BEGIN,編譯子例程,然後執行任何其他代碼。無論代碼是在之內還是之外之內,模塊對代碼之外的模塊幾乎沒有影響。

+0

所以,其實我沒有節省時間。只需將相同的工作量(一次執行)轉移到程序中的不同點。謝謝。 – 2012-03-07 14:26:15

+0

@SérgioBernardino確切的。 Class :: Accessor使用非常類似的技術,用閉包構建訪問器,只是具有更大的靈活性。 Class :: Accessor :: Fast幾乎就是你寫的。麋可能會這樣做,或者它可能會評估一個字符串,我不確定。 – Schwern 2012-03-07 22:01:23

1

如果您將軟件包分別存放到自己的文件並使用use,則區別將消失:模塊的代碼正在編譯時運行。

3

在上面的例子中,如果我沒有弄錯,類是由在 運行時,與在同一時間被創建它的存取作爲 類被實例化。這意味着在創建對象時會有一個速度 。

您說的是訪問器是在運行時創建的,但是在顯示的代碼中,它們只是在執行開始時創建一次 - 當然不在實例化的時候。你可以看到構造函數的作用:

sub new { bless {}, shift } 

這是相當簡短和相應快。對訪問器構建循環應用BEGIN塊只是將工作從運行時間的開始移動到編譯時間結束,並且您什麼也沒有做到。你在基準測試中得到的變化是微不足道的,我認爲主要是由於噪聲。


我已複製我自己的系統提高了運行時間爲10秒的基準,並在四個測試得到的結果如下。看起來,添加一個BEGIN塊會提高性能,但這是一個很小的改進,我無法立即解釋它。

     Rate accessors_new accessors_begin 
accessors_new 1463771/s    --    -1% 
accessors_begin 1476583/s    1%    -- 


        Rate accessors_new accessors_begin 
accessors_new 1469833/s    --    -0% 
accessors_begin 1472234/s    0%    -- 


        Rate accessors_new accessors_begin 
accessors_new 1454942/s    --    -1% 
accessors_begin 1469680/s    1%    -- 


        Rate accessors_new accessors_begin 
accessors_new 1462613/s    --    -1% 
accessors_begin 1473985/s    1%    -- 
+0

謝謝。 「關於_」將BEGIN塊應用於訪問器構建循環只是將工作從編譯時間結束時移動到運行時間開始時間「_確實推動了@schwern這一點,並且也讓我所做的只是將工作從一個地方到另一個地方沒有有效地優化任何事 – 2012-03-07 14:26:35

+1

那句話也是錯的!我的意思是一個'BEGIN'塊將工作從運行時間移動到編譯時間,但你知道!重點是它只做了一次,而不是像你想象的那樣在每個實例化中完成。 – Borodin 2012-03-07 19:44:16