在Perl中,生成getter和setter而不是硬编码是有缺点的吗?

时间:2009-09-07 16:25:52

标签: perl class module oop

在下面的示例模块中,通过向符号表添加匿名子例程来生成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;

7 个答案:

答案 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可以做得更好 - 但我还没有尝试过。