Saturday, June 13, 2009

Why make_immutable is recommended for Moose classes

Someone on perlmonks asked
Can you point me to a good explanation of why make_immutable is recommended?
And I realized in the documentation we really only say (in Moose::Manual::BestPractices)
making classes immutable speeds up a lot of things, most notably object construction.
So instead of burying my explanation deep inside Perlmonks I thought I would explain it here (and add to my Iron Man creds).

So, Moose metaclasses are built specifically so that they can be altered at any time from anywhere and still remain a valid and correct class. This is why there is no __PACKAGE__->finalize_class or similar type of method call required at the end of your Moose class definition. But doing things this way does come at a price in that some of the meta-level calls can be very expensive.

For instance, if you wanted to know all the attributes supported by a class, you would need to collect all the local attributes, then visit each superclass (recursively) and collect all those attributes while being sure to skip all overridden attributes. This can get quite expensive and since we allow for you to, at any time, alter the inheritance structure or add/delete attributes via the MOP, this means we can not cache the results of that query (well we could cache it, but then we would have to have all sorts of extra code to check the cache and invalidate it, etc. etc.).

So what you are doing when you make a Moose class immutable, is actually saying "it is okay to cache things, I am not going to mess with the metaclass". At that point Moose takes the opportunity to memoize many of the MOP calls and install methods that throw exceptions when you try and alter the metaclass, effectively making the class read-only. However, this really only helps speed up calls to ->meta methods, so we also then take it one step further.

The example I gave above, of checking all attributes in a class, may seem kind of esoteric and not something one usually needs to care about, but this is exactly what Moose needs to do every time it creates an instance of an object. It needs to do this in order to properly initialize all the slots in an instance, fire any triggers, check any type constraints, perform any type coercions and call all BUILD methods in the inheritance graph in the correct order. By memoizing the computed list of all inherited attributes we are actually saving quite a lot of computation, but honestly that is not enough. So we actually take the opportunity to inline and compile our own optimized constructor method that does the exact same thing, but in much less time. The result is that object construction is significantly faster during the runtime of the program (which is when it really counts) and we instead take the compile-time hit of the code construction and evaluation. And since we are in there already we also inline a DESTROY method which correctly calls all the DEMOLISH methods in the correct order (Moose already, by default, will inline your attribute accessors, but if it didn't then it would do that as well).

So the short answer is that making your class immutable is good because it memoizes several metaclass methods and installs an optimized constructor and destructor for your class and therefore helps reduce a fair amount of the cost (during runtime) of all the abstraction that the MOP provides.

No comments:

Post a Comment