我想知道当我使用反射通过字符串调用方法时,实际上发生了什么:
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 -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
的内容在调用之间可能会发生更改,因此必须每次执行该查找操作。"bar"
作为方法名称,因此避免了在每次执行时加载变量并检查其内容。首先,我不相信我没有看到的基准测试。很容易出错。我自己对它们进行了基准测试。
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 对于这个操作来说有些过长了。但为了确保,可以与像加1这样的小操作进行比较。
$ 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纳秒吗?我猜不会。
$metref = \&{"Class::$method"};
$instance = new Class;
$instance->$metref(@args);
$metref = \&Class::myMethod
。还有使用sub { ... }
的闭包,Perl可能会比符号解引用更有效地处理它们。请参见this perlmonks discussion和perlref : Making References。Class::->can($method)
来获取代码引用。它遵循继承并且可以在严格模式下使用。此外,$metref->($instance, @args)
甚至更快。 - Eric Strom
@ISA
层次结构的符号链中查找方法,并在运行时调度。 - Axeman@ISA
(或符号表)修改、重新bless
对象等。此外,perlobj
描述了方法查找为运行时缓存,这乍一看似乎表明探测@ISA
不能解释速度差异。 - pilcrow