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