Re: Moosifying Test-Run Part 2: Probably Not

[email protected] (Stevan Little)
Newsgroups perl.moose
Message-ID <[email protected]>
Shlomi,

On Jun 1, 2008, at 8:57 AM, Shlomi Fish wrote:
> I said that I'd like to convert Test-Run to Moose. Well, now I'm in
> the midst of the translation - many tests fail, but the problem is
> that the tests now run much more slowly. It is a noticable difference,
> that makes running the tests unbearable.

I have found that this is typical of sufficiently complex  
Class::Accessor modules, the coding style promoted by Class::Accessor  
seems to not match well with Moose. In particular where passing undef  
values into the accessors is concerned (which is almost certainly  
where your test failures are coming from) Moose will not allow this  
(a "Str" type is not "a string, but possibly undef too"). Also proper  
use of lazy & default/builder with Moose will help make instance  
generation quicker, especially in the cases where you don't always  
need every slot to be initialized. In those cases too, using the  
"predicate" option (documented in Class::MOP::Attribute) will help as  
well. Here is an example of this actually:

Class::Accessor version:

   {
       package Foo;
       __PACKAGE__->mk_accessors('bar');
   }

   my $foo = Foo->new; # no "bar" initialized
   if ($foo->bar) { ... } # this check is cheap, because your just  
checking the accessor

Moose version:

   {
       package Foo;
       use Moose;

       has 'bar' => (is => 'rw', lazy => 1, default => 'FOO::BAR',  
predicate => 'has_bar');
   }

   my $foo = Foo->new; # no "bar" initialized
   if ($foo->bar) { ... } # this check is NOT cheap, because your  
forcing the deferred value in bar to be created
   if ($foo->has_bar) { ... } # this check *is* cheap, because your  
just checking slot existence

Now, in the above example thats not really that much of a difference,  
but if 'bar' were are large complex data structure or some  
expensively computed value, then it would make a huge difference.

It should be pointed out that the most recent Moose/Class::MOP  
release has a speedup which showed approx. 20-25% better performance,  
so things are improving. And people have seen anything from a 3x to  
10x times performance increase by making their classes immutable  
(depending largely on the complexity of the classes I think, no one  
has cared enough to benchmark and figure it out).

> The problem as I see it is that I'm using Plug-ins instead of Roles
> and that I didn't finalise the class. Of course, I'd rather not
> finalise the class, or ditch away plug-ins.

I think perhaps you misunderstand the use of the "immutable" feature.  
First, it is not finalizing, in the C# sense of the word, where it  
prohibits subclassing and such. Making a class immutable means that  
the metaclass can longer be altered, it allows Moose to then create  
an inlined constructor as well as memoize several of the more  
commonly needed bits of data in the metaclass. This is much less  
restrictive then you might think, and unless you are doing lots of - 
 >meta hacking, you will never need to care.

I am not sure how you are doing plugins, but there are two ways to go  
about this in the Moose world utilizing roles.

1) Compile-time

This is plugins that you can know about during class construction  
time, and are *class* specific. So you simply create your class,  
applying the roles for the plugins. Often times, this is enough and  
runtime pluggable-ness is really overkill and not actually necessary.  
The key thing about this is that it is *class* specific, meaning you  
will want several instances of a specific class (with a specific set  
of plugins applied to it) in your application.  This may require  
creating a few extra subclasses, but really if your application  
doesn't require arbitrary combinations of plugins applied to  
arbitrary instances of varying classes, then this may actually help  
increase the clarity of you design.

Now, since this is all compile-time defined, obviously it does not  
hamper making a class immutable.

2) Run-time

Now, sometimes your application design *does* require arbitrary  
combinations of plugins applied to arbitrary instances of varying  
classes, in this case you really do need a plugin system. In this  
case you can still use roles, you just apply them to an instance  
instead of to a class. Of course this is a more expensive operation  
because Moose needs to create an anon-class for you and apply the  
role to that, but you asked for it, and so you need to pay the cost.  
Moose will cache specific combinations of class+roles and try to  
reuse the anon-classes if possible, and it does help if you apply all  
the roles at once and not in succession.

Now, even though this is all done at runtime, you can still make your  
underlying classes immutable and get some of the performance benefits  
it provides.


> And naturally, Moose is
> also making many run-time checks that are not present in my old
> Class-Accessor and are not really necessary for me. As much as I
> appreciate type-checking for instance members, I'm disciplined enough
> for it not to matter much.

So just remove the types, it is that simple since they are entirely  
optional. In fact, the accessor generated by Moose for this:

   has foo => (is => 'rw');

if *faster* the the typical Class::Accessor version since it never  
creates a lexical $self, and just uses $_[0] instead. Moose does as  
much as it can to only make you pay for the features you use, but it  
cannot remove the cost of features you use, but just don't really  
want ;)

> So Moose seems impressive, but an overkill for Test-Run's needs.

I think perhaps you have not given Moose enough of a chance, and that  
your making this judgement before properly understanding what Moose  
can provide for you. Any powerful tool (like Moose), if not properly  
used, will seem like overkill.

> So unless Moose will become faster, I won't be able to use it in
> Test-Run, and will have to rely on Class-Accessor and the additional
> logic I've built on top of it.

Moose is becoming faster and that is one of our primary goals right  
now. However in your case, by using type checks you don't want and  
not making your classes immutable, *you* are the one slowing your  
application down. The tools are right in front of you, you need only  
to use them.

> From what I've heard of CPython, they
> often re-implemented Python libraries code in C to make them faster.
> Perhaps the Perl world should follow suit.

We have some C/XS in Class::MOP and the inclusion of that it provided  
our last major speed boost actually. There are possibly a few other  
places we could recode in C/XS that would provide us some more  
benefits, but since I am not (and never will be) a C programmer, all  
I can say is

... patches welcome :)


- Stevan
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.