And the fastest OO accessor is...

There's a lot of FUD out there about the performance of various OO modules, particularly Mouse. So let's set it straight with some benchmarking.

I've chosen to simulate that buggaboo of OO performance in Perl, the simple accessor. The one that you're going to call millions of times and that you'll be sorely tempted to reimplement with a hash or tear all the argument checks out of for "performance". To make it a little more realistic, I'm checking both getting and setting as well as a simple argument check.

Each benchmark gets or sets an integer once via an accessor (except in the case of the plain hash). The object is created outside the benchmark to avoid distortion.

Each accessor validates that the argument is an integer.

I picked the following modules because they are either popular or make performance claims:

  • Mouse, both XS and pure Perl
  • Moose
  • Moo, with and without Sub::Quote
  • A plain hash
  • A class written by hand (aka "manual")

I also threw in Object::Tiny and Object::Tiny::XS, even though it's not really fair. They're read only and do no argument checks. They sacrifice everything for performance. Let's see if it's worth it.

Finally, I did a hash with no argument checks, just to get an upper bound.

I won't clog this post with the full benchmark code, but here's what the Moose class looks like for example.

package Foo::Moose;
use Moose;
has bar => (is => 'rw', isa => "Int");
__PACKAGE__->meta->make_immutable;

Results

And the results, from slowest to fastest getter with the cheaters at the bottom.

Name                  Get            Set
-----------------------------------------
Moo                   -11%            -4%   
manual                  0%             0%    
Mouse, no XS            0%           -33%  
Moo w/quote_sub         5%           -46%  
Moose                   8%           -40%  
Mouse                 145%           468%  

manual, no check        0%           153%  
Object::Tiny           20%           n/a   
Object::Tiny::XS      226%           n/a    
hash, no check        279%         1,116%  
hash                  289%           147%

That's with Perl 5.12.2 on OS X using the latest versions of all those modules as of this writing (Moose 1.24, Mouse 0.91, Moo 0.009007, Object::Tiny 1.08, Object::Tiny::XS 1.01).

The percentages are the % improvement from a hand written class (manual).

You can get all the data as a CSV.

Conclusions

The go-to module is clearly Mouse with XS. The power of Moose, but faster than everything equivalent, faster than writing it by hand, faster (on setting with equivalent validation) than a raw hash, and no required dependencies. Unless you need the meta capabilities of Moose, there's little reason to use anything else.

If you want a read-only object with no argument checks, use Object::Tiny::XS, hands down.

Object::Tiny, on the other hand, is pretty poky; significantly slower than Mouse with XS. Given its extreme limitations, if you can use XS then there's no point to Object::Tiny.

Moose and pure Perl Mouse stack up about the same and fare well against a hand written class, though lose out in setting. You should be getting far more than setting, so you won't be seeing much performance gain by hand writing methods.

The big surprise is the newcomer, Moo... but not in a good way. It's "an extremely light-weight, high-performance Moose replacement" but the numbers don't stack up. Absolutely creamed by Mouse with XS, it's the slowest getter of them all with a slight edge in setting.

Another surprise is Moo's quote_sub. This is supposed "to create coderefs that are inlineable, giving us a handy, XS-free speed boost." The Moo docs suggest using them for "isa" type checks, so I did. The numbers don't pan out, it's worse than a regular sub. I'm going to assume it's a bug and I've filed a report. Curiously, using quote_sub on isa made the getter faster... which makes no sense.

The final conclusion is this: performance is no longer an excuse for not using OO. Accessors are what will be called most often and they're what tempts programmers to micro optimize and throw out their abstractions. Mouse and Object::Tiny::XS can blow the doors off what you can write by hand. The performance improvements in Mouse show that abstraction not only makes the code cleaner, but it allows radical optimizations which you can have just by upgrading to a new version.

Bias

Every benchmark has its biases. Here's the ones in this one that I can identify.

Of course the version of Perl, operating system and versions of modules all matter. One particularly large gap will be in Mouse. Its XS optimizations are a fairly recent thing, so older versions of Mouse will not perform well.

The benchmark uses a very simple type check, an integer. I chose that because it's what an awful lot of methods are going to use. Because it's so simple and common Moose, and especially Mouse, have a clear opportunity to optimize for it. A less common or more complicated type check might have impacted performance more. A string might provide different numbers.

Finally, I really like Mouse. :-)

Update

Corrected my statement about Mouse being faster than a raw hash. That only happens when setting with validation. When I originally did the benchmarks they combined setting and getting.

Added a wider conclusion about the role of performance in choosing an OO system.

9 Comments

You should check App::Benchmark::Accessors - it's tests output a benchmark table so you can see the benchmarks in many Perl versions and OSes. For example: http://www.cpantesters.org/cpan/report/cd1a6aa6-2a9b-11e0-aaf8-39cd950acfcc

Quote from tsee (he cant log in right now - adjectives altered to make sense in context here):

Wrong conclusion. Faster than a raw hash? Only if you plan to do parameter validation. Seriously, if you do, pick another language OR don't worry about this micro-optimization.

You missed out on the fastest implementation of non-argument-checking accessors available. By proxy, you happened to use part of it via Object::Tiny::XS and surprise, for what Object::Tiny::XS does, it is the fastest.

You should take a look at Class::XSAccessor or Class::XSAccessor::Array.

Thank you for these tests! As it happens I've gotten used to the Moose meta object stuff. Perhaps I'll wean myself of it to gain the Mouse speed, someday.

What demerphq said - Class::XSAccessor deserves inclusion.

I would like a mutation of Class::XSAccessor that calls out to Perl for its setting but stays in XS for getting; I wonder if it exists.

quote: "There's a lot of FUD out there about the performance of various OO modules, particularly Mouse. So let's set it straight with some benchmarking."

I thought it was now common-wisdom that the overall run-time performance of most applications is generally not limited by the object system, but by external IO, design decisions etc.

You didn't link to or mention which FUD you are responding to, so I'm not sure exactly what you are straightening out. However I think your narrow focus on accessors ignores a the use-cases which are not dependent on accessor speed for their "performance."

Command-line apps and one-shot CGI scripts are more sensitive to "startup cost" for example. How about a follow-up post looking at that? And for completeness one could also investigate the relative time for getter/setter calls against DBI calls, or system calls, or ...

Perl 5.21? I am so out of date!

I don't see where you get the idea that Mouse with XS is faster than a "raw hash", whatever that means (hash ref? inside out objects?). With no check on assignments, raw hash appears to be twice as fast as Mouse XS. (279 vs 145, 1116 vs 468)

For the record, my personal FUD is more about memory (and possibly CPU (if that makes sense (I don't think it does))) usage.

The largest project I've worked on (which could really benefit from moving to Moose) is deployed on a farm of low-resource machines... single < 1GHz CPU, some machines (seriously (I'm not joking)) 128 MB RAM.

Granted, I think those extremely low-models have mostly been phased out for machines that are closer to 512 or maybe 1G RAM and the CPU's are probably getting better, but I'm also thrashing huge amounts of data for comparison.

I appreciate this benchmark, but I'd also love to see one based on memory usage (guess I should get on that...)

Leave a comment

About Michael G Schwern

user-pic Ya know, the guy who tells you to write tests and not use MakeMaker.