As is well known in Perl is not very convenient support for object-oriented programming. If you want to program with classes, then a lot has to be done manually. However, Perl has very rich extensibility, so over time, many libraries (packages) provide support for classes, methods, and properties with syntax of varying degrees of convenience. But as it turned out, these packages lose in performance compared with the manual implementation of OOP constructs. Those. on the one hand, they are pleasant to use, and on the other, they make the code slower. I always wanted to know how slower the code becomes, and which of these packages should be used and which ones should not. So I decided to do a little research.
Overview of OOP Packages
First, let's take a quick look at the available OOP packages for Perl.
Just perl
Without any packages, you can create a class in Perl like this:
package Dog; sub new { my ($class, %self) = @_; bless \%self, $class; return \%self; } sub make_noise { my $self = shift; say $self->{name}, " says: ruff-ruff!"; } 1;
Then this class can be used as follows:
use Dog; my $dog = Dog->new(name => "Snoopy"); $dog->make_noise();
Moose
Moose ideas from Perl6 lay in the basics of
Moose . Using it to create classes much easier. The previous example, rewritten using Moose, will look much better:
package Dog; use Moose; has name => (is => 'ro', isa => 'Str'); sub make_noise { my $self = shift; say $self->name(), " says: ruff-ruff!"; } __PACKAGE__->meta->make_immutable;
As you can see, it is no longer necessary to explicitly create the
new method, and properties are now described much easier and more naturally. But all is not smooth. We have to manually unpack the
$ self parameter in each class method. In general, the methods remained simply Perl procedures. I wish they looked more like normal methods.
')
Method :: Signatures
And then this wonderful package comes to our aid:
Method :: Signatures . I advise you to read the documentation on it - there are many pleasant surprises.
With it, our code gets even better:
package Dog; use Moose; use Method::Signatures; has name => (is => 'ro', isa => 'Str'); method make_noise() { say $self->name(), " says: ruff-ruff!"; } __PACKAGE__->meta->make_immutable;
MooseX :: Declare
The inquisitive minds of Perl's adepts did not stop there, but went much further and wrote
MooseX :: Declare . A truly wonderful package, if it were not for one drawback (about which later). Now we can achieve complete nirvana in the description of our
Dog class:
use MooseX::Declare; class Dog { has name => (is => 'ro', isa => 'Str'); method make_noise() { say $self->name(), " says: ruff-ruff!"; } }
OOP packet performance
Perl allows you to achieve almost complete naturalness in the description of classes. But do not rejoice. It turns out this naturalness leads to a loss of performance. To find out how, compare such characteristics as:
- Creating a class object.
- Method call
- Access to the property.
And so begin.
Benchmark package will be used for testing. In the test results, the notation is as follows:
- vanilla is pure Perl with no additional packages.
- moose - with Moose package,
- moose_sig - with Moose and Method :: Signatures,
- moosex - with the MooseX package.
Object creation
Rate moose_sig moose moosex vanilla
moose_sig 104102 / s - -0% -0% -74%
moose 104383 / s 0% - -0% -74%
moosex 104592 / s 0% 0% - -74%
vanilla 401924 / s 286% 285% 284% -
As you can see, creating objects using all OOP packages is almost 4 times slower than on pure Perl. But there is one good news: the operation of creating an object using Moose and MooseX :: Declare takes place in the same time. Therefore, if you use one of these packages, then preference must be given to MooseX :: Declare.
Method call
Let's move on to the next item, the method call. Here the picture is not so remarkable:
Rate moosex moose moose_sig vanilla
moosex 8596 / s - -94% -94% -95%
moose 148055 / s 1622% - -0% -17%
moose_sig 148516 / s 1628% 0% - -17%
vanilla 178254 / s 1974% 20% 20% -
MooseX loses Moose (with or without Signatures) 16 times! This is of course a big surprise. If you search the Internet, you may find that the reason for such a big difference in performance is the MooseX :: Method :: Signatures package. Therefore, until the situation improves for the better, MooseX, as it is, cannot be used.
However, the good news is that Moose + Method :: Signatures is only 20% slower than pure Perl. Therefore, you can safely use these packages without any significant loss of performance.
Property access
We turn to the last item of the tests, access to the property of the object:
Rate moosex moose_sig moose vanilla
moosex 1503608 / s - -1% -1% -17%
moose_sig 1517928 / s 1% - -0% -16%
moose 1517928 / s 1% 0% - -16%
vanilla 1815063 / s 21% 20% 20% -
Again, losing all packages compared to pure Perl is only 20%. As you can see, here MooseX was not worse than other packages. If only not for MooseX :: Method :: Signatures ...
Fix moosex
I'd like to use MooseX, but he is very prickly. Let's try to correct it so that it is at least a little bit better when calling methods.
The first thing that comes to mind is to use Method :: Signatures with MooseX :: Declare. Rewrite the
Dog code as follows:
use MooseX::Declare; class Dog { use Method::Signatures; has name => (is => 'ro', isa => 'Str'); method make_noise() { say $self->name(), " says: ruff-ruff!"; } }
This approach works, but leads to an ugly warning:
Subroutine Dog :: method redefined at /opt/local/lib/perl5/site_perl/5.14.1/darwin-thread-multi-2level/Devel/Declare/MethodInstaller/Simple.pm line 17.
Prototype mismatch: sub Dog :: method: none vs (&) at /opt/local/lib/perl5/site_perl/5.14.1/darwin-thread-multi-2level/Devel/Declare/MethodInstaller/Simple.pm line 17.
The fact is that
method is a simple Perl function that is defined by both MooseX :: Method :: Signatures and Method :: Signatures. But it is determined with different prototypes, whence there is a warning. Unfortunately, I did not find how to remove this warning. So I had to come up with another solution.
Method :: Signatures :: Simple
What could be easier!
Method :: Signatures :: Simple is a simplified version of Method :: Signatures that has one good feature. You can set your own name for the
method function in it, thus avoiding a conflict with MooseX :: Method :: Signatures.
Using this package, the
Dog will look like this:
use MooseX::Declare; class Dog { use Method::Signatures::Simple name => 'def'; has name => (is => 'ro', isa => 'Str'); def make_noise() { say $self->name(), " says: ruff-ruff!"; } }
The difference is that instead of
method, we now write
def (as in Python).
Let's see if the performance of our code has improved (moosex_sig):
Rate moosex moosex_sig moose_sig moose vanilla
moosex 8651 / s - -94% -94% -94% -95%
moosex_sig 146152 / s 1589% - -1% -3% -18%
moose_sig 148025 / s 1611% 1% - -2% -17%
moose 150866 / s 1644% 3% 2% - -16%
vanilla 179200 / s 1971% 23% 21% 19% -
Our expectations were met. MooseX :: Declare + Method :: Signature :: Simple gives the same performance as Moose + Method :: Signatures.
Conclusion
Despite all the riches of choice, there is only one alternative. Today, the best recommendation from my point of view for using OOP in Perl is the combination of the MooseX :: Declare and Method :: Signatures :: Simple packages. It allows you to describe classes in a natural way and has a not very large, one might even say, quite reasonable price in terms of performance.
Addendum: source code for tests is available on github:
github.com/alexeiz/oopbench