2011-12-02 16 views
5

我想知道當我使用反射來調用,名字我作爲一個字符串的方法到底發生了什麼:

my $foo = Foo->new(); 
my $method = 'myMethod'; 
$foo->$method(); 

是〜比原生電話慢20%:

$foo->myMethod(); 

關於perl反射如何實現的文檔的任何指針都會有幫助。

謝謝。

+3

我認爲非常明顯的是,不需要包含對特定方法的引用的操作碼,它需要更多的指令來查找'@ ISA'層次結構的符號鏈中的方法,並在運行時調度。 – Axeman

+1

@Axeman,方法調度是動態的 - 考慮運行時的'@ISA'(或符號表)修改,重新放棄對象等。另外,['perlobj'](http://perldoc.perl.org/ perlobj.html)將方法查找描述爲運行時緩存,它首先表明探測'@ ISA'不能解釋速度差異。 – pilcrow

回答

4

首先,我不信任我看不到的基準。弄錯他們太容易了。我自己做了基準測試。

use strict; 
use warnings; 

use Benchmark qw(cmpthese); 

sub new { return bless({}, $_[0]); } 
sub myMethod { } 

my %tests = (
    rt => '$foo->$method() for 1..1000;', 
    ct => '$foo->myMethod() for 1..1000;', 
); 

$_ = 'use strict; use warnings; our $foo; our $method; ' . $_ 
    for values(%tests); 

our $foo = __PACKAGE__->new(); 
our $method = 'myMethod'; 

cmpthese(-3, \%tests); 

我可以複製你的結果。

 Rate rt ct 
rt 1879/s -- -19% 
ct 2333/s 24% -- 

(Rate is 1/1000th of actual rate.) 

這看起來確實很大,但百分比可能非常容易讓人產生誤解。我們來看看絕對時間的差異。

Compile-time: 2333000 calls per second = 429 nanoseconds per call 
Run-time:  1879000 calls per second = 532 nanoseconds per call 
Difference: 103 nanoseconds per call. 

並不多。那麼,那段時間花在哪裏?

$ perl -MO=Concise,-exec -e'$foo->myMethod()'  $ perl -MO=Concise,-exec -e'$foo->$method()' 
1 <0> enter         = 1 <0> enter 
2 <;> nextstate(main 1 -e:1) v:{    = 2 <;> nextstate(main 1 -e:1) v:{ 
3 <0> pushmark s        = 3 <0> pushmark s 
4 <#> gvsv[*foo] s       = 4 <#> gvsv[*foo] s 
               + 5 <#> gvsv[*method] s 
5 <$> method_named[PV "myMethod"]    ! 6 <1> method K/1 
6 <1> entersub[t2] vKS/TARG     = 7 <1> entersub[t3] vKS/TARG 
7 <@> leave[1 ref] vKP/REFC     = 8 <@> leave[1 ref] vKP/REFC 
-e syntax OK         = -e syntax OK 

它似乎唯一的區別是一個額外的符號表查找。 100ns似乎過分。但可以肯定的是,比較小的東西,比如加一個。

$ perl -MO=Concise,-exec -e'my $y = $x;'  $ perl -MO=Concise,-exec -e'my $y = $x + 1;' 
1 <0> enter        = 1 <0> enter 
2 <;> nextstate(main 1 -e:1) v:{   = 2 <;> nextstate(main 1 -e:1) v:{ 
3 <#> gvsv[*x] s       = 3 <#> gvsv[*x] s 
              + 4 <$> const[IV 1] s 
              + 5 <2> add[t3] sK/2 
4 <0> padsv[$y:1,2] sRM*/LVINTRO   = 6 <0> padsv[$y:1,2] sRM*/LVINTRO 
5 <2> sassign vKS/2      = 7 <2> sassign vKS/2 
6 <@> leave[1 ref] vKP/REFC    = 8 <@> leave[1 ref] vKP/REFC 
-e syntax OK        = -e syntax OK 

堵代碼和our $x = 100;到基準以上代碼,我們得到

  Rate addition baseline 
addition 4839/s  --  -26% 
baseline 6532/s  35%  -- 

(Rate is 1/1000th of actual rate.) 

所以,

Basline: 6553000/s = 153 nanoseconds per assignment 
Addition: 4839000/s = 207 nanoseconds per assignment+addition 
Difference:    54 nanoseconds per addition 

因此,它是合理的,一個簡單的符號表查找爲採取兩次只要添加一個?可能是因爲它涉及散列字符串並在短鏈表中尋找字符串。

你真的在意在這裏和那裏花費額外的100ns嗎?不,我在猜測。

+0

+1爲一個冗長的「過早優化!」 :-) – Tanktalus

10
> perl -MO=Concise -e '$foo->$bar' 
8 <@> leave[1 ref] vKP/REFC ->(end) 
1  <0> enter ->2 
2  <;> nextstate(main 1 -e:1) v:{ ->3 
7  <1> entersub[t3] vKS/TARG ->8 
3  <0> pushmark s ->4 
-  <1> ex-rv2sv sKM/1 ->5 
4   <#> gvsv[*foo] s ->5 
6  <1> method K/1 ->7    # ops to read $bar and then call method 
-   <1> ex-rv2sv sK/1 ->6  # 
5    <#> gvsv[*bar] s ->6  # 
-e syntax OK 

> perl -MO=Concise -e '$foo->bar' 
7 <@> leave[1 ref] vKP/REFC ->(end) 
1  <0> enter ->2 
2  <;> nextstate(main 1 -e:1) v:{ ->3 
6  <1> entersub[t2] vKS/TARG ->7 
3  <0> pushmark s ->4 
-  <1> ex-rv2sv sKM/1 ->5 
4   <#> gvsv[*foo] s ->5 
5  <$> method_named[PV "bar"] ->6 # op to call the 'bar' method 
-e syntax OK 

在第一示例中,Perl已經加載$bar變量,然後檢查,看它是否包含可以用作方法的名稱或值。由於$bar的內容可能在調用之間發生變化,因此每次都必須執行此查找。

在第二個示例中,perl已經知道字符串"bar"應該用作方法名稱,所以這樣可以避免在每次執行時加載變量並檢查其內容。

但是,您不應該擔心兩個本地操作之間的速度差異達到20%。主要是因爲本機操作速度非常快,而且實際要求的速度很快就會被代碼執行的實際算法所淹沒。換句話說,除非你已經用代碼分析器將這個問題作爲一個熱點進行了分離,否則速度差異比實際重要性更具教育意義。

1

您可以通過使用方法參考,ALA加快這:

$metref = \&{"Class::$method"}; 
$instance = new Class; 
$instance->$metref(@args); 

你可以明顯使用$metref = \&Class::myMethod,而是如果你知道在編譯時的方法名。也有使用sub { ... }的封閉,perl可能比你的符號解引用更高效。見this perlmonks discussionperlref : Making References

+5

更好地寫'Class :: - > can($ method)'來獲得coderef。它尊重繼承和嚴格的工作。此外,'$ metref - >($ instance,@args)'更快。 –