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