在下面的示例模块中,通过向符号表添加匿名子例程来生成getter和setter。在以这种方式创建方法之后,生成的代码在功能上是否等同于(在行为,速度等方面)具有手动编写的getter和setter的模块,或者这种方法是否具有某种固有的责任? (我已经做了一些基本的速度基准测试,到目前为止还没有发现任何差异。)
package Module;
use strict;
use warnings;
BEGIN {
my @attr = qw(author title number);
no strict 'refs';
for my $a (@attr){
*{__PACKAGE__ . "::get_$a"} = sub { $_[0]->{$a} };
*{__PACKAGE__ . "::set_$a"} = sub { $_[0]->{$a} = $_[1] };
}
}
sub new {
my $class = shift;
bless { @_ }, $class;
}
1;
答案 0 :(得分:8)
运行时性能应该没有区别,如果在两种情况下得到的代码都相同。但是,这通常是不可能的,除非您使用字符串eval
来创建子例程。例如,您提供的代码:
... = sub { $_[0]->{$a} };
将比您手动编写的代码慢得多:
sub foo { $_[0]->{'foo'} }
只是因为前者必须在将变量$ a作为哈希的键之前获取变量$ a的值,而后者使用常量作为其哈希键。另外,shift
通常比$_[0]
快。use Benchmark qw(cmpthese);
package Foo;
sub manual_shift { shift->{'foo'} }
sub manual_index { $_[0]->{'foo'} }
my $attr = 'foo';
*dynamic_shift = sub { shift->{$attr} };
*dynamic_index = sub { $_[0]->{$attr} };
package main;
my $o = bless { foo => 123 }, 'Foo';
cmpthese(-2, {
manual_shift => sub { my $a = $o->manual_shift },
manual_index => sub { my $a = $o->manual_index },
dynamic_shift => sub { my $a = $o->dynamic_shift },
dynamic_index => sub { my $a = $o->dynamic_index },
});
。这是一些基准代码:
Rate dynamic_index manual_index dynamic_shift manual_shift
dynamic_index 1799024/s -- -3% -4% -7%
manual_index 1853616/s 3% -- -1% -4%
dynamic_shift 1873183/s 4% 1% -- -3%
manual_shift 1937019/s 8% 4% 3% --
以及我系统上的结果:
eval "sub eval_index { \$_[0]->{'$attr'} }";
eval "sub eval_shift { shift->{'$attr'} }";
他们非常接近以至于差异可能会在噪音中消失,但在许多试验中,我认为你会发现“手动换档”变体是最快的。但是像所有这样的微基准测试一样,你必须在你的硬件和你的perl版本上测试你的确切场景,以确保任何事情。
这里的字符串eval被投入混合。
Rate dynamic_index manual_index dynamic_shift manual_shift eval_shift eval_index
dynamic_index 1820444/s -- -1% -2% -3% -4% -5%
manual_index 1835005/s 1% -- -1% -2% -3% -4%
dynamic_shift 1858131/s 2% 1% -- -1% -2% -3%
manual_shift 1876708/s 3% 2% 1% -- -1% -2%
eval_shift 1894132/s 4% 3% 2% 1% -- -1%
eval_index 1914060/s 5% 4% 3% 2% 1% --
它应该与“手动”变体完全相同,加上或减去统计噪声。我的结果:
shift
同样,这些都非常接近,你必须付出巨大的努力,并进行许多试验,以便从噪音中挑选信号。但是使用常量作为散列键和使用变量(必须首先检索其值)作为散列键之间的区别应该显示出来。 ({{1}}优化是一个单独的问题,更有可能在过去或未来的perl版本中以某种方式改变。)
答案 1 :(得分:7)
没有区别因为:
sub Some_package::foo { ... }
只是简写:
BEGIN { *Some_package::foo = sub { ... } }
来自perlmod
的参考资料答案 2 :(得分:6)
良好生成的访问器的主要缺点是它们打败了依赖静态分析的工具。例如,IDE的方法自动完成。如果这是一个大项目的一部分,我衷心建议你看看Moose。它的访问器生成正确(以及更多)。它很受欢迎,IDE正在添加支持,因此上述问题将在适当的时候消失。
CPAN上有许多访问器生成器易于使用并生成适度高效的代码。如果性能是一个问题,那么 - 如果你坚持使用访问器方法 - 你不能比Class::XSAccessor更快,因为它为访问器使用高度优化的C / XS代码。
滚动自己的访问者生成代码是所有选项中最糟糕的。它永远地打破了静态分析,可能很难阅读,并可能引入新的错误。
答案 3 :(得分:2)
这两种方法都有在编译时将子例程引用安装到符号表中的结果。行为和运行时性能将完全相同。编译时可能存在非常小的(即可忽略的)差异。
类似的方法是通过AUTOLOAD
按需生成访问者,这在运行时确实有很小的影响。使用AUTOLOAD
方法还可以更改$object->can()
等内容的行为。
显然,生成方法会将它们隐藏在任何形式的静态分析中,包括像ctags这样的工具。
答案 4 :(得分:2)
运行时行为和性能应该几乎相同(除非你做的事关心方法是否是闭包)。
使用大量属性,编译时间和内存使用会有所不同......两者都支持生成的getter和setter,而不是手动编写的getter和setter。 例如,试试这个:
BEGIN {
no strict 'refs';
for my $a ("aaaa".."zzzz"){
*{__PACKAGE__ . "::get_$a"} = sub { $_[0]->{$a} };
*{__PACKAGE__ . "::set_$a"} = sub { $_[0]->{$a} = $_[1] };
}
}
print `ps -F -p $$`; # adjust for your ps syntax
与
相比sub get_aaaa { $_[0]->{aaaa} }
sub set_aaaa { $_[0]->{aaaa} = $_[1] }
sub get_aaab { $_[0]->{aaab} }
...
sub set_zzzy { $_[0]->{zzzy} = $_[1] }
sub get_zzzz { $_[0]->{zzzz} }
sub set_zzzz { $_[0]->{zzzz} = $_[1] }
print `ps -F -p $$`; # adjust for your ps syntax
答案 5 :(得分:2)
唯一的区别是启动时间。对于简单的代码生成方案,差异很难衡量。对于更复杂的系统,它可以加起来。
行动中的一个很好的例子是Moose。 Moose为您提供各种惊人的代码生成,但它对启动时间有重大影响。这足以解决Moose开发人员在pmc文件中处理a scheme to cache generated code并加载它们而不是每次都重新生成代码的问题。
还要考虑Class::Struct之类的内容。它使用字符串eval进行代码生成(上次我检查过)。即便如此,因为它非常简单,在启动时不会导致显着的减速。
答案 6 :(得分:2)
除了其他人提到的优点之外,我还想补充一下我发现的主要缺点:它们都在探查器中显示为匿名子程序。无论出于何种原因,Devel::DProf只是不知道如何找出名称。
现在我希望希望新的Devel::NYTProf可以做得更好 - 但我还没有尝试过。