代码之家  ›  专栏  ›  技术社区  ›  FMc TLP

在Perl中,是否存在生成getter和setter而不是硬编码它们的缺点?

  •  7
  • FMc TLP  · 技术社区  · 15 年前

    在下面的示例模块中,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 回复  |  直到 8 年前
        1
  •  8
  •   John Siracusa    15 年前

    运行时性能应该没有区别 如果 结果代码在两种情况下都是相同的。但是,这通常是不可能的,除非使用字符串 eval 创建子例程。例如,您提供的代码:

    ... = sub { $_[0]->{$a} };
    

    会比手动编写的代码慢很多:

    sub foo { $_[0]->{'foo'} }
    

    这仅仅是因为前者必须在将变量$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%            --
    

    它们是如此的接近以至于噪音中的差异可能会消失,但是在许多试验中,我认为你会发现“手动换档”的变体是最快的。但是和所有这样的微基准一样,您必须在硬件和Perl版本上测试您的具体场景,以确定是否存在任何问题。

    这里有一个字符串eval加入到混合中。

    eval "sub eval_index { \$_[0]->{'$attr'} }";
    eval "sub eval_shift { shift->{'$attr'} }";
    

    它应该与“手动”变体完全相同,加上或减去统计噪声。我的结果是:

                       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%         --
    

    再说一次,这些都是如此接近,你将不得不付出巨大的努力,并进行许多试验,以从噪音中找出信号。但是使用常量作为散列键和使用变量(必须首先检索其值)作为散列键之间的区别应该是显而易见的。(The 转移 优化是一个单独的问题,在过去或将来的Perl版本中,更可能以某种方式进行更改。)

        2
  •  7
  •   AndyG    8 年前

    没有区别,因为:

    sub Some_package::foo { ... }
    

    只是以下内容的简写:

    BEGIN { *Some_package::foo = sub { ... } }
    

    借鉴 perlmod

        3
  •  6
  •   tsee    15 年前

    生成良好的访问器的主要缺点是它们击败了依赖静态分析的工具。例如,您的IDE方法自动完成。如果这是一个大项目的一部分,我衷心建议你看看 Moose .它是正确的访问器代(以及更多)。在IDES中添加支持已经足够流行了,因此前面提到的问题将在适当的时候消失。

    CPAN上有许多访问器生成器,它们易于使用并生成中等效率的代码。如果性能是一个问题,那么——只要您坚持使用访问器方法——您就不能比 Class::XSAccessor 因为它为访问器使用了高度优化的C/XS代码。

    滚动自己的访问器生成代码是所有选项中最糟糕的。它永久地破坏了静态分析,很可能很难阅读,并可能引入新的错误。

        4
  •  2
  •   Michael Carman    15 年前

    这两种方法都是在编译时将子例程引用安装到符号表中的结果。行为和运行时性能将完全相同。编译时的差异可能非常小(即可以忽略不计)。

    类似的方法是通过 AUTOLOAD 这对运行时的影响很小。使用 自动装填 方法也可以改变诸如 $object->can() .

    显然,生成方法会将它们隐藏在任何形式的静态分析中,包括ctags等工具。

        5
  •  2
  •   ysth    15 年前

    运行时行为和性能应该基本相同(除非您做一些关心方法是否是闭包的事情)。

    有了大量的属性,编译时间和内存使用都会有所不同……这都有利于生成的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
    
        6
  •  2
  •   daotoad    15 年前

    唯一的区别是启动时间。对于简单的代码生成方案,差异将很难测量。对于更复杂的系统,它可以加起来。

    一个很好的例子就是 Moose .Moose为您生成了各种令人惊奇的代码,但它对启动时间有着重要的影响。这已经够了驼鹿开发人员正在研究的一个问题了 a scheme to cache generated code 在PMC文件中加载它们,而不是每次都重新生成代码。

    也可以考虑 Class::Struct . 它使用字符串eval(上次我检查)生成代码。即使如此,因为它非常简单,它不会在启动时造成明显的减速。

        7
  •  2
  •   Dominic Mitchell    15 年前

    除了其他人提到的优秀点之外,我还想添加我发现的主要缺点:它们都显示为探查器中的匿名子例程。不管什么原因, Devel::DProf 只是不知道怎么知道名字。

    现在我愿意 希望 那个新的 Devel::NYTProf 可能做得更好,但我没试过。