使用反射时性能中断$ foo-> $ bar()

时间:2011-12-02 14:03:45

标签: performance perl reflection

我想知道当我使用反射来调用名称为字符串的方法时会发生什么:

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

比原生呼叫慢〜20%:

$foo->myMethod();

有关如何实现perl反射的文档的任何指针都会有所帮助。

感谢。

3 个答案:

答案 0 :(得分: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 :(得分: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

简单的符号表查找需要两倍的时间才能添加一个吗?可能,因为它涉及散列字符串并在短链接列表中查找字符串。

你真的关心在这里和那里多花100英镑吗?不,我猜。

答案 2 :(得分:1)

您可以使用方法参考ala:

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

如果您在编译时知道方法名称,显然可以使用$metref = \&Class::myMethod。还有使用sub { ... }的闭包,perl可能比符号解除引用更有效。请参阅this perlmonks discussionperlref : Making References