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,
BEGIN
blocks as an efficient way of doing this sort of class manipulation?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.
Rate accessors_begin accessors_new
accessors_begin 1865476/s -- -4%
accessors_new 1943339/s 4% --
Rate accessors_begin accessors_new
accessors_begin 1978799/s -- -1%
accessors_new 2001062/s 1% --
Rate accessors_new accessors_begin
accessors_new 1943339/s -- -2%
accessors_begin 1988089/s 2% --
Rate accessors_begin accessors_new
accessors_begin 1796509/s -- -8%
accessors_new 1949296/s 9% --
Rate accessors_begin accessors_new
accessors_begin 1916122/s -- -3%
accessors_new 1969595/s 3% --
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 use
d, require
d or eval
ed goes through it's own compile and runtime steps. use Some::Class
causes Some/Class.pm
to go through both compile and runtime executing BEGIN
, 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.
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.
You are correct to say that the accessors are created at run time, but in the code you show they are created once only at the start of execution - certainly not at the point of instantiation. You can see what the constructor does:
sub new { bless {}, shift }
which is pretty brief and correspondingly fast. Applying a BEGIN
block to the accessor build loop just moves the work from the beginning of run time to the end of compilation time, and you have achieved nothing. The variation you are getting in your benchmarks are insignificant and I presume mostly due to noise.
I have reproduced your benchmarks on my own system raising the run time to 10 seconds, and got the following results over four tests. It seems that the addition of a BEGIN block does fractionally increase performance but it is a minimal improvement and I am unable to explain it immedately.
Rate accessors_new accessors_begin
accessors_new 1463771/s -- -1%
accessors_begin 1476583/s 1% --
Rate accessors_new accessors_begin
accessors_new 1469833/s -- -0%
accessors_begin 1472234/s 0% --
Rate accessors_new accessors_begin
accessors_new 1454942/s -- -1%
accessors_begin 1469680/s 1% --
Rate accessors_new accessors_begin
accessors_new 1462613/s -- -1%
accessors_begin 1473985/s 1% --
If you separate the packages to files of their own and use use
, the difference is away: a module's code is being run in compile time.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With