我想知道當我使用反射來調用,名字我作爲一個字符串的方法到底發生了什麼:
my $foo = Foo->new();
my $method = 'myMethod';
$foo->$method();
是〜比原生電話慢20%:
$foo->myMethod();
關於perl反射如何實現的文檔的任何指針都會有幫助。
謝謝。
我想知道當我使用反射來調用,名字我作爲一個字符串的方法到底發生了什麼:
my $foo = Foo->new();
my $method = 'myMethod';
$foo->$method();
是〜比原生電話慢20%:
$foo->myMethod();
關於perl反射如何實現的文檔的任何指針都會有幫助。
謝謝。
首先,我不信任我看不到的基準。弄錯他們太容易了。我自己做了基準測試。
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嗎?不,我在猜測。
+1爲一個冗長的「過早優化!」 :-) – Tanktalus
> 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%。主要是因爲本機操作速度非常快,而且實際要求的速度很快就會被代碼執行的實際算法所淹沒。換句話說,除非你已經用代碼分析器將這個問題作爲一個熱點進行了分離,否則速度差異比實際重要性更具教育意義。
您可以通過使用方法參考,ALA加快這:
$metref = \&{"Class::$method"};
$instance = new Class;
$instance->$metref(@args);
你可以明顯使用$metref = \&Class::myMethod
,而是如果你知道在編譯時的方法名。也有使用sub { ... }
的封閉,perl可能比你的符號解引用更高效。見this perlmonks discussion和perlref : Making References。
更好地寫'Class :: - > can($ method)'來獲得coderef。它尊重繼承和嚴格的工作。此外,'$ metref - >($ instance,@args)'更快。 –
我認爲非常明顯的是,不需要包含對特定方法的引用的操作碼,它需要更多的指令來查找'@ ISA'層次結構的符號鏈中的方法,並在運行時調度。 – Axeman
@Axeman,方法調度是動態的 - 考慮運行時的'@ISA'(或符號表)修改,重新放棄對象等。另外,['perlobj'](http://perldoc.perl.org/ perlobj.html)將方法查找描述爲運行時緩存,它首先表明探測'@ ISA'不能解釋速度差異。 – pilcrow