Showing posts with label mop. Show all posts
Showing posts with label mop. Show all posts

Wednesday, September 2, 2009

Moose Startup Time over Time

I actually owe marcus an apology.

You were correct in that Moose startup time has not gotten significantly faster since 0.01. After our exchange Sartak decided to actually take a look and see. So he wrote this script, which produced this data, which jhannah then promptly turned into this graph (reproduced below).

It seems that since the 0.15 release we have mostly stayed within the 0.2 to 0.3 second range. It is interesting to look at the Changelogs for both Moose and Class::MOP you can actually see the feature additions or refactorings that correspond with the peaks and valleys.

Some of the most recent speedup is the direct result of Goro (gfx) Fuji's work on Class::MOP and Moose as sponsored by the Japan Perl Association. Much thanks to all involved in that.

Thursday, July 16, 2009

More Thoughts on Parameterized Roles

So my last post on parameterized roles has really got me thinking. One of the first use cases I ran into for parameterized roles was MooseX::Storage. Myself and Chris Prather wrote it on the train into NYC one day and since there was no such thing as MooseX::Role::Parameterized yet we hacked it with an exported Storage subroutine which composing in multiple roles based on the parameters that were passed to it. This is actually still how MooseX::Storage works because, well, it Just Works tm so there is no need to change it. But I decided as a thought experiment and a test of the Role Functor idea from my last post to see if I could re-write MooseX::Storage in terms of it. Much to my delight it not only worked, but came out very cleanly. 

Now, this is still written in MooseX::Declare inspired pseudo-code, so it is not yet a reality, but I am getting more and more convinced that this is something I really need to write. So anyway, here goes.

role COLLAPSER { requires 'pack', 'unpack' }
role FORMATTER { requires 'thaw', 'freeze' }
role IO        { requires 'load', 'store'  }

role DefaultCollapser with COLLAPSER {

    method pack {
        Collapser::Engine->new( object => $self )
                         ->collapse_object
    }

    method unpack ($class:, $data) {
        Collapser::Engine->new( class => $class )
                         ->expand_object( $data )
    }
}

role JSONFormatter [ 
        Collapser => (does => COLLAPSER) 
    ] with FORMATTER {

    method thaw ($class:, $json) {
        $class->unpack( JSON::Any->encode( $json ) )
    }

    method freeze {
        JSON::Any->decode( $self->pack )
    }
}

role SimpleFile [ 
        Formatter => (does => FORMATTER) 
    ] with IO {

    method load ($class:, $filename){
        my $fh   = IO::File->new( $filename, 'r' );
        my $data = do { local $/; <$fh>; };
        $class->thaw( $data );
    }

    method store ($filename) {
        my $fh = IO::File->new( $filename, 'w' );
        $fh->print( $self->freeze );
    }
}

I am obviously punting on a couple of details here to keep things simple for the example, but I think it gets the point across.  The nice part, in my opinion, is that the parameterization nicely captures the "levels" of serialization. For instance, here is what a class that does all the options would look like:

class Point 
 with SimpleFile( 
          Formatter => JSONFormatter( 
              Collapser => DefaultCollapser 
         ) 
    ) {
    has x => (is => rw, isa => Int, default => 0);
    has y => (is => rw, isa => Int, default => 0);

    method clear {
        $self->x(0);
        $self->y(0);
    }
}

And here is a class which does not do the load/store but just does the JSON freeze/thaw:

class Point
 with JSONFormatter( 
          Collapser => DefaultCollapser 
    ) {
    has x => (is => rw, isa => Int, default => 0);
    has y => (is => rw, isa => Int, default => 0);

    method clear {
        $self->x(0);
        $self->y(0);
    }
}

And here is a class which does only the simple pack/unpack:

class Point with DefaultCollapser {
    has x => (is => rw, isa => Int, default => 0);
    has y => (is => rw, isa => Int, default => 0);

    method clear {
        $self->x(0);
        $self->y(0);
    }
}

Overall I am quite happy with this, so now it is just a matter of finding the tuits to actually implement it.

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.