FORWARD NOTE: Please, for the sake of this discussion, let’s ignore for a moment the fact that the same end can be achieved with recourse to Class::Accessor, or even simply by using Moose (probably with better results when accounting for code readability and maintainability).
With regards to object oriented Perl, the book Programming Perl discusses the ability of generating accessor methods with closures. For example, this is a valid piece of code:
#!perl
use v5.12;
use warnings;
# at run-time
package Person1;
my @attributes = qw/name age address/;
for my $att ( @attributes )
{
my $accessor = __PACKAGE__ . "::$att";
no strict 'refs'; # allow symbolic refs to typeglob
*$accessor = sub {
my $self = shift;
$self->{$att} = shift if @_;
return $self->{$att};
};
}
sub new { bless {}, shift }
package main;
use Data::Dumper;
my $dude = Person1->new;
$dude->name('Lebowski');
say Dumper($dude);
On the above example, if I am not mistaken, the class is composed at run-time, with its accessors being created at the same time as the class is being instantiated. This means that there will be a speed penalty on object creation.
Now consider the following alternative:
#!perl
use v5.12;
use warnings;
package Person2;
BEGIN
{
for my $att (qw/name age address/)
{
my $accessor = __PACKAGE__ . "::$att";
no strict 'refs'; # allow symbolic refs to typeglob
*$accessor = sub {
my $self = shift;
$self->{$att} = shift if @_;
return $self->{$att};
};
}
}
sub new { bless {}, shift }
package main;
use Data::Dumper;
my $dude = Person2->new;
$dude->name('Lebowski');
say Dumper($dude);
On this version, the composition is made within a BEGIN block (ie, at compile-time), and I believe that by dealing with this task as soon as possible on the life-cycle of the program, I am saving time during object instantiation on run-time.
A simple Benchmark,
# benchmark it!
package main;
use Benchmark qw/cmpthese/;
cmpthese(-2, {
accessors_new => sub { Person1->new },
accessors_begin => sub { Person2->new },
});
seemingly supports my theory with these results:
Rate accessors_begin accessors_new
accessors_begin 853234/s -- -9%
accessors_new 937924/s 10% --
Assuming that my reasoning has been correct so far,
- What other benefits/drawbacks exist when comparing both of these strategies?
- Is it a good idea to depend on
BEGINblocks as an efficient way of doing this sort of class manipulation? - When is it not recommended?
When I run your benchmark I get quite a bit of flutter which could account for your differences. For anything 10% difference or less, run it several times to be sure.
But really all you’re benchmarking is
sub new { bless {}, shift }. Benchmarking the same thing against itself is going to emphasize flutter. The work generating the accessors has already been done when the code loaded and never comes into it, BEGIN block or not.Perl does not have a single compile-time and run-time. Rather, each thing
used,required orevaled goes through it’s own compile and runtime steps.use Some::ClasscausesSome/Class.pmto go through both compile and runtime executingBEGIN, compiling subroutines and then executing any other code. Whether code is inside or outside a BEGIN block within a module makes little difference to code outside that module.