Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Perl are there disadvantages to generating getters and setters rather than hard-coding them?

In the example module below, the getters and setters are generated by adding anonymous subroutines to the symbol table. After the methods have been created in this manner, will the resulting code be functionally equivalent (in terms of behavior, speed, etc.) to a module with manually-written getters and setters, or does this approach have some sort of inherent liability? (I have done some basic speed benchmarking and have not detected any differences so far.)

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;
like image 367
FMc Avatar asked Sep 07 '09 16:09

FMc


4 Answers

There should be no difference in runtime performance if the resulting code is the same in both cases. This is usually not possible, however, unless you use string eval to create your subroutines. For example, the code you provided:

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

will be ever-so-slightly slower than the code you would have written manually:

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

simply because the former has to get the value of the variable $a before using it as a key to the hash, whereas the later uses a constant as its hash key. Also, as an aside, shift usually tends to be faster than $_[0]. Here's some benchmark code:

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 },
});

and the results on my system:

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

They're so close that differences may get lost in the noise, but over many trials I think you'll see that the "manual shift" variant is the fastest. But as with all microbenchmarks like this, you have to test your exact scenario on your hardware and your version of perl to be sure of anything.

And here's string eval thrown into the mix.

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

It should be exactly the same as the "manual" variants, plus or minus the statistical noise. My results:

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

Again, these are all so close that you'd have to take great pains and perform many trials to sort out the signal from the noise. But the difference between using a constant as a hash key and using a variable (whose value must first be retrieved) as a hash key should show through. (The shift optimization is a separate issue and is more likely to change one way or the other in past or future versions of perl.)

like image 199
John Siracusa Avatar answered Oct 18 '22 00:10

John Siracusa


There is no difference because:

sub Some_package::foo { ... }

is just a shorthand for:

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

Reference from perlmod

like image 36
draegtun Avatar answered Oct 17 '22 23:10

draegtun


The main drawback of well-generated accessors is that they defeat tools that rely on static analysis. Your IDE's method auto-completion for example. If this is part of a big project, I heartily recommend you take a look at Moose. It's accessor generation done right (and much more). It is popular enough that support is being added to IDEs, so that the aforementioned issue will disappear in due time.

There are many accessor generators on CPAN that are easy to use and generate moderately efficient code. If performance is an issue, then -- provided you stick to using accessor methods -- you can't get any faster than Class::XSAccessor since it uses highly optimized C/XS code for the accessors.

Rolling your own accessor-generating code is the worst of all options. It defeats the static analysis forever, is likely rather hard to read, and potentially introduces new bugs.

like image 25
tsee Avatar answered Oct 18 '22 00:10

tsee


Both approaches have the result of installing a subroutine reference into the symbol table at compile time. The behavior and runtime performance will be exactly the same. There might be a very small (i.e. negligible) difference in compile time.

A similar approach is to generate accessors on demand via AUTOLOAD, which does have a small impact at runtime. Using the AUTOLOAD approach can also change the behavior of things like $object->can().

Obviously, generating methods will hide them from any form of static analysis, including tools like ctags.

like image 25
Michael Carman Avatar answered Oct 17 '22 23:10

Michael Carman