Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Moose, is it safe to access member variables directly through the object's internal hash?

Tags:

perl

moose

Moose objects seem to be blessed hashrefs with member variables as entries. Is it safe to use hash-syntax to access the members? All I want is a simple ability to get/set a member variables, but I find the speed of the accessors generated by perl to be unacceptable for my application, to the point where I am considering rewriting it in non-moose OO. Yes, I know optimizing is a crime punishable by death, but I have a billion+ line file and adding a simple line-counter variable adds billion+ function calls, and that can add hours onto a single run.

Example to show slowness (100x):

package Bullwinkle;
use strict;
use warnings;
use Moose;

has 'x' => (is => 'rw', default => 0);

# counter is also slow
has 'counter' => (
    traits  => ['Counter'],
    is      => 'ro',
    isa     => 'Num',
    default => 0,
    handles => {
        inc_counter   => 'inc',
    },
);

package main;
use strict;
use warnings;
use Benchmark qw/timethese cmpthese/;

my $bull = Bullwinkle->new();
my $hash = { x => 0 };

cmpthese(-1,{ 
    bull => sub{
        $bull->x(1 + $bull->x());
    },
    bull_direct => sub{
        ++$bull->{x}
    },
    bull_counter => sub{
        $bull->inc_counter();
    },
    hash => sub{
        ++$hash->{x}
    },
});
  
                   Rate         bull bull_counter         hash  bull_direct
bull           983040/s           --         -28%         -91%         -92%
bull_counter  1362629/s          39%           --         -88%         -88%
hash         11251965/s        1045%         726%           --          -3%
bull_direct  11650844/s        1085%         755%           4%           --
like image 298
user1481 Avatar asked Dec 09 '22 01:12

user1481


1 Answers

I would say it's never safe to break encapsulation, Moose or not.

Note that your bull test is not 100 times slower. Remember that it's using the accessor twice. Just on the back of the envelope, that takes it from 1,000,000 runs per second to 2,000,000 accesses per second. Compared to the hash, that makes it about a factor of 6. Add a bunch of those slowdowns and it can add up, though.

However, I don't think Moose is the problem there. When I try it with a non-Moose class that does the same thing, the results aren't that much better. It looks like most of the slowdown is just from normal object-oriented method calls. The difference between the non-Moose and Moose classes using inc_counter is about a factor of 2.

Benchmarks are really tricky things. You really need to work hard to tease out the parts that are different and ensure you test appropriate base cases. When you get wild numbers, you should start as a skeptic and adjust things until they aren't that wild. For instance, even if Moose is slow, I don't think anyone would use it if it was a slow as you surmised. A little slower is one thing, and orders or magnitude is a whole other thing.


I was also curious about the benchmark and I wanted to try it with make_immutable. Here's my result for a default Perl5.14.2 with Moose-2.0403 on my Mac Pro running Lion. Bull is your original code, Rocky uses make_immutable, and Natasha is a non-Moose class that does the same thing:

                      Rate  bull rocky natasha bull_counter rocky_counter natasha_x rocky_x bull_x natasha_counter rocky_direct bull_direct natasha_direct hash
bull              728177/s    --   -6%    -17%         -42%          -43%      -65%    -66%   -67%            -71%         -93%        -94%           -94% -95%
rocky             771011/s    6%    --    -12%         -39%          -39%      -63%    -64%   -65%            -70%         -93%        -93%           -94% -95%
natasha           877713/s   21%   14%      --         -30%          -31%      -58%    -59%   -60%            -66%         -92%        -92%           -93% -94%
bull_counter     1260308/s   73%   63%     44%           --           -1%      -40%    -42%   -42%            -51%         -88%        -89%           -89% -91%
rocky_counter    1274310/s   75%   65%     45%           1%            --      -39%    -41%   -42%            -50%         -88%        -89%           -89% -91%
natasha_x        2105717/s  189%  173%    140%          67%           65%        --     -3%    -4%            -17%         -81%        -81%           -82% -85%
rocky_x          2163925/s  197%  181%    147%          72%           70%        3%      --    -1%            -15%         -80%        -81%           -82% -85%
bull_x           2184533/s  200%  183%    149%          73%           71%        4%      1%     --            -14%         -80%        -80%           -82% -85%
natasha_counter  2548621/s  250%  231%    190%         102%          100%       21%     18%    17%              --         -77%        -77%           -79% -82%
rocky_direct    10901037/s 1397% 1314%   1142%         765%          755%      418%    404%   399%            328%           --         -3%            -9% -24%
bull_direct     11202734/s 1438% 1353%   1176%         789%          779%      432%    418%   413%            340%           3%          --            -6% -21%
natasha_direct  11939231/s 1540% 1449%   1260%         847%          837%      467%    452%   447%            368%          10%          7%             -- -16%
hash            14252488/s 1857% 1749%   1524%        1031%         1018%      577%    559%   552%            459%          31%         27%            19%   --

Here's my program:

#!/Users/brian/bin/perls/perl5.14.2
use v5.10.1;
use strict;
use warnings;

use Benchmark qw/timethese cmpthese/;

package Bullwinkle {
    use strict;
    use warnings;
    use Moose;

    has 'x' => (is => 'rw', default => 0);

    # counter is also slow
    has 'counter' => (
        traits  => ['Counter'],
        is      => 'ro',
        isa     => 'Num',
        default => 0,
        handles => {
            inc_counter   => 'inc',
        },
    );
    }

package Rocky {
    use strict;
    use warnings;
    use Moose;

    has 'x' => (is => 'rw', default => 0);

    # counter is also slow
    has 'counter' => (
        traits  => ['Counter'],
        is      => 'ro',
        isa     => 'Num',
        default => 0,
        handles => {
            inc_counter   => 'inc',
        },
    );

    __PACKAGE__->meta->make_immutable;
    }

package Natasha {
    use strict;
    use warnings;

    sub new { bless { 'x' => 0 }, $_[0] }
    sub inc_counter { $_[0]->{x} += 1 }
    sub x           { 
        if( defined $_[1] ) { $_[0]->{x} = $_[1] }
        else                { $_[0]->{x} }
        }
    }

my $bull    = Bullwinkle->new;
my $rocky   = Rocky->new;
my $natasha = Natasha->new;

my $hash  = { 'x' => 0 };

cmpthese(-1,{ 
    bull          => sub { $bull->x(1 + $bull->x )   },
    bull_x        => sub { $bull->x                  },
    bull_direct   => sub { ++$bull->{'x'}            },
    bull_counter  => sub { $bull->inc_counter        },

    rocky         => sub { $rocky->x(1 + $rocky->x ) },
    rocky_x       => sub { $rocky->x                 },
    rocky_direct  => sub { ++$rocky->{'x'}           },
    rocky_counter => sub { $rocky->inc_counter       },

    natasha         => sub { $natasha->x(1 + $natasha->x ) },
    natasha_x       => sub { $natasha->x                   },
    natasha_direct  => sub { ++$natasha->{'x'}             },
    natasha_counter => sub { $natasha->inc_counter         },

    hash          => sub { ++$hash->{'x'}            },
});
like image 67
brian d foy Avatar answered Dec 28 '22 22:12

brian d foy