Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using closures to modify classes within Perl BEGIN blocks

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 BEGIN blocks as an efficient way of doing this sort of class manipulation?
  • When is it not recommended?
like image 932
Sérgio Bernardino Avatar asked Mar 07 '12 12:03

Sérgio Bernardino


3 Answers

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 used, required or evaled 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.

like image 70
Schwern Avatar answered Nov 04 '22 02:11

Schwern


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%              --
like image 34
Borodin Avatar answered Nov 04 '22 01:11

Borodin


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.

like image 24
choroba Avatar answered Nov 04 '22 01:11

choroba