Sunday, February 15, 2009

How fast or slow is Moose?

Moose is a postmodern object system for Perl 5 that takes the tedium out of writing object-oriented Perl. It borrows all the best features from Perl 6, CLOS (LISP), Smalltalk, Java, BETA, OCaml, Ruby and more, while still keeping true to its Perl 5 roots. It is very powerful and I was curious how fast is in current state. I have read that Moose is slow but all articles what I have found is about two years old. For me is important mostly runtime speed but compile time. So my benchmark is focused only to runtime. I also hate getter/setter combined accessors thus my tests are also only about separated getter and setter. Benchmark code follows.

#!/usr/bin/env perl

use strict;
use warnings;
use Data::Dumper;
use Benchmark qw(:all :hireswallclock);

{

 package MooseClassMutable;
 use Moose;

 has var => (
  is       => 'ro',
  reader   => 'get_var',
  writer   => 'set_var',
  required => 1
 );

}
{

 package MooseClassImmutable;
 use Moose;

 has var => (
  is       => 'ro',
  reader   => 'get_var',
  writer   => 'set_var',
  required => 1
 );

 no Moose;
 __PACKAGE__->meta->make_immutable;
}
{

 package PerlClass;

 sub new {
  my ( $class, %args ) = @_;
  die 'var value must be set' unless exists $args{var};
  return bless \%args, $class;
 }

 sub get_var { shift()->{var} }

 sub set_var { $_[0]->{var} = $_[1] }
}

{

 package MooseClassFast;
 use Moose;
 with 'MooseX::Emulate::Class::Accessor::Fast';

 has var => (
  is       => 'ro',
  required => 1
 );

 __PACKAGE__->follow_best_practice;
 __PACKAGE__->mk_accessors('var');

 no Moose;
 __PACKAGE__->meta->make_immutable;
}

cmpthese(
 -5,
 {   map {
   my $class = $_;
   "new $class" => sub { $class->new( var => 1 ) for 1 .. 1000 }
   } qw(MooseClassMutable MooseClassImmutable PerlClass MooseClassFast)
 }
);
my %objs = ( map { $_ => $_->new( var => 1 ) }
  qw(MooseClassMutable MooseClassImmutable PerlClass MooseClassFast) );
cmpthese(
 -5,
 {   map {
   my $class = $_;
   my $obj   = $objs{$class};
   "get $class" => sub { $obj->get_var() for 1 .. 1000 }
   } qw(MooseClassMutable MooseClassImmutable PerlClass MooseClassFast)
 }
);
cmpthese(
 -5,
 {   map {
   my $class = $_;
   my $obj   = $objs{$class};
   "set $class" => sub { $obj->set_var(1) for 1 .. 1000 }
   } qw(MooseClassMutable MooseClassImmutable PerlClass MooseClassFast)
 }
);

I have tested Moose versions 0.54 and 0.68 and just for curiosity also Class::Accessor::Fast emulation which works only with Moose version 0.68. Notice that rate is measured in thousands. Moose 0.54 results comes first.

                          Rate new MooseClassMutable new MooseClassImmutable new PerlClass
new MooseClassMutable   6.85/s                    --                    -96%          -98%
new MooseClassImmutable  192/s                 2697%                      --          -53%
new PerlClass            403/s                 5790%                    111%            --
                          Rate get MooseClassMutable get MooseClassImmutable get PerlClass
get MooseClassMutable   1716/s                    --                     -2%          -25%
get MooseClassImmutable 1754/s                    2%                      --          -23%
get PerlClass           2273/s                   32%                     30%            --
                          Rate set MooseClassImmutable set MooseClassMutable set PerlClass
set MooseClassImmutable 1611/s                      --                   -2%          -16%
set MooseClassMutable   1643/s                      2%                    --          -14%
set PerlClass           1916/s                     19%                   17%            --

Moose version 0.68 follows.

                          Rate new MooseClassMutable new MooseClassFast new MooseClassImmutable new PerlClass
new MooseClassMutable   15.4/s                    --               -84%                    -92%          -96%
new MooseClassFast      98.7/s                  541%                 --                    -48%          -76%
new MooseClassImmutable  190/s                 1138%                93%                      --          -54%
new PerlClass            412/s                 2579%               318%                    116%            --
                          Rate get MooseClassFast get MooseClassImmutable get MooseClassMutable get PerlClass
get MooseClassFast      1716/s                 --                     -2%                   -2%          -24%
get MooseClassImmutable 1743/s                 2%                      --                   -1%          -23%
get MooseClassMutable   1754/s                 2%                      1%                    --          -22%
get PerlClass           2261/s                32%                     30%                   29%            --
                          Rate set MooseClassFast set MooseClassMutable set MooseClassImmutable set PerlClass
set MooseClassFast      78.6/s                 --                  -95%                    -95%          -96%
set MooseClassMutable   1659/s              2011%                    --                     -1%          -15%
set MooseClassImmutable 1680/s              2038%                    1%                      --          -14%
set PerlClass           1950/s              2381%                   18%                     16%            --

Moose seems fast enough for me. If I realize how powerful Moose is results are great. I can persist class definition using make_immutable in most of cases and 190 thousand object constructions per second is enough. There is also big improvement in mutable version between 0.54 and 0.68 and 15 thousand per second is not terrible. Moose accessors are really fast and make_immutable have not any impact here. 1.7 million reads and 1.6 million writes per second is enough and my ugly handcrafted accessors can't make big difference here (2.2Mr/s and 1.9Mw/s). There is strange Class::Accessor:Fast setter result and I'm curious why. Anyway Moose itself performs well and there is not reason using it.

2 comments:

Dave Rolsky said...

Moose inlines all accessors by default, so make_immutable is irrelevant. Your benchmark does make that quite clear.

Dave Rolsky said...

Sorry to double post but ...

You may not realize this, but MooseX::Emulate::Class::Accessor::Fast just uses Moose under the hood, so its accessor should be identical to regular Moose. It's useful if you have a module already using Class::Accessor::Fast (CAF) which you'd like to convert over to Moose.

It was written for Catalyst 5.80, which will be Moose-based. I think the goal is to allow plugins which use the CAF API to work with the new Catamoose.