我想知道当我使用反射调用其名称作为字符串的方法时究竟会发生什么:
my $foo = Foo->new();
my $method = 'myMethod';
$foo->$method();
比本地调用慢20%:
$foo->myMethod();
任何关于perl反射是如何实现的文档的指针都是有帮助的。
谢谢。
> 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%的速度差异。主要是因为本机操作非常快,它们实际需要的速度很快就会与代码必须执行的实际算法相比相形见绌。换句话说,除非您将此问题作为一个代码分析器的热点孤立出来,否则速度差异的教学意义大于实际意义。
首先,我不相信我没有看到的基准。太容易弄错了。我自己对它们进行了基准测试。
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
那么查找一个简单的符号表要比添加一个符号表花费两倍的时间合理吗?有可能,因为它涉及到对字符串进行散列并在短链表中查找字符串。
你真的在乎在这里和那里多花100毫秒吗?不,我猜。
您可以通过使用方法引用ala:
来加快速度。$metref = &{"Class::$method"};
$instance = new Class;
$instance->$metref(@args);
如果在编译时知道方法名,显然可以使用$metref = &Class::myMethod
。也有使用sub { ... }
的闭包,perl可能比符号解引用更有效地处理它们。参见perlmonks的讨论和perlref: Making References。