diff --git a/perl-oo-action-benchmark.html b/perl-oo-action-benchmark.html index f0405c3..b8255f5 100644 --- a/perl-oo-action-benchmark.html +++ b/perl-oo-action-benchmark.html @@ -9,50 +9,114 @@ << back
- I've spent a lot of time working in projects that use the Action pattern (also called the Command pattern).
- Now, regardless of how i've land on OO vs Functional in the last 5 years, there has always been a question I have yet to answer: "How much does this abstraction cost,
- and more importantly, how much does object instantiation actually cost?" (since the action pattern has you instantiating A LOT of objects)
+ I've spent a lot of time working in projects that use the Action pattern (also called the Command pattern).
+ Now, regardless of how i've land on OO vs Functional in the last 5 years, there has always been a question I have yet to answer: "How much does this abstraction cost,
+ and more importantly, how much does object instantiation actually cost?" (since the action pattern has you instantiating A LOT of objects)
- So I set out to benchmark, Perl OO, using the traditional Module OO, Corinna, Moo and Moose. But I thought it may also
- provide some value to compare these findings to other languages, namely ones in the same sort of realm that Perl has lives in: Ruby and Raku (formerly Perl 6).
+ So I set out to benchmark, Perl OO, using the traditional Module OO, Corinna, Moo and Moose. But I thought it may also
+ provide some value to compare these findings to other languages, namely ones in the same sort of realm that Perl has lives in: Ruby and Raku (formerly Perl 6).
- The benchmark is fairly simple, since the goal here is to test the actual instantiation costs, so the method associated with the implemented action is very simple.
- An Action object has two members, name and age. It's execute method returns a Map/Hash/Dictionary, with the following keys
- { name => self.name, age => self.age }. Basically just accessing the instantiated objects age and name attributes, and associating them to
- keys of the same name.
+ The benchmark is fairly simple, since the goal here is to test the actual instantiation costs, so the method associated with the implemented action is very simple.
+ An Action object has two members, name and age. It's execute method returns a Map/Hash/Dictionary, with the following keys
+ { name => self.name, age => self.age }. Basically just accessing the instantiated objects age and name attributes, and associating them to
+ keys of the same name.
- One-million Action objects will be instantiated in a O(n) loop, and executed, then destroyed all within the same scope. Like so:
+ One-million Action objects will be instantiated in a O(n) loop, and executed, then destroyed all within the same scope. Like so:
-
+
my $name = 'Tim';
my $age = 12;
for ( 1 .. 1000000 ) {
my $user = Action->new( name => $name, name => $age )->execute();
}
-
+
- This will be performed one-hundred times, then, the average of the times taken to do the one-million iterations will be the benchmark result. - It is also important to note that the initial load-time of the language run-times is included in the calculation, though they are mostly negligble especially - in the Perl case, and the actual instantiation takes the vast-majority of the time (close to 99% on average). + This will be performed one-hundred times, then, the average of the times taken to do the one-million iterations will be the benchmark result. + It is also important to note that the initial load-time of the language run-times is included in the calculation, though they are mostly negligble especially + in the Perl case, and the actual instantiation takes the vast-majority of the time (close to 99% on average).
- This benchmark was performed on an Intel i7 12700KF, using DDR5 memory. + This benchmark was performed on an Intel i7 12700KF, using DDR5 memory.
+| Language | +Performance | +
|---|---|
| + Perl Module OO + | ++ 454.627ms + | +
| + Perl Module OO + Types + | ++ 505.276ms + | +
| + Moo + | ++ 960.03ms + | +
| + Moo + Types + | ++ 2308.226ms + | +
| + Moose + | ++ 11420.455ms + | +
| + Moose + Types + | ++ 13649.37ms + | +
| Ruby | +167.627ms | +
| Raku (MoarVM start-up subtracted) | +801.947ms | +
- Using the traditional Perl approach to OO using ref's and modules, is by far the fastest. + Using the traditional Perl approach to OO using ref's and modules, is by far the fastest.
-
+
package Action;
use strict;
@@ -81,26 +145,26 @@ my $age = 12;
for ( 1 .. 1000000 ) {
my $user = Action->new( $name, $age )->execute();
}
-
+
- This implementation is alright, it's fast, but it doesn't provide any validations, which is what I assume most OO frameworks like Moo and Moose do, especially
- if you use modules like Type::Tiny.
+ This implementation is alright, it's fast, but it doesn't provide any validations, which is what I assume most OO frameworks like Moo and Moose do, especially
+ if you use modules like Type::Tiny.
-
+
PERL TIME : 454.627ms
-
+
- Considering my assumptions about other OO libraries I decided to add a few checks to the Module OO implementation,
- using Scalar::Util we can test if age is a number, and also ensure the defined-ness of our
- attributes using the built-in defined.
+ Considering my assumptions about other OO libraries I decided to add a few checks to the Module OO implementation,
+ using Scalar::Util we can test if age is a number, and also ensure the defined-ness of our
+ attributes using the built-in defined.
-
+
package Action;
use strict;
@@ -139,22 +203,22 @@ my $age = 12;
for ( 1 .. 1000000 ) {
my $user = Action->new( $name, $age )->execute();
}
-
+
This only came at a net-cost of around 50ms extra on average, which is surprisingly fast.
-
+
PERL + TYPES TIME : 505.276ms
-
+
- Corinna is a new OO system added to Perl 5.38 but still marked as experimental. It performs very well compared to the features it provides, - but it is definitely not something you'll see in the wild too often. + Corinna is a new OO system added to Perl 5.38 but still marked as experimental. It performs very well compared to the features it provides, + but it is definitely not something you'll see in the wild too often.
-
+
use feature 'class';
no warnings;
@@ -172,29 +236,29 @@ my $age = 12;
for ( 1 .. 1000000 ) {
Action->new( name => $name, age => $age )->execute();
}
-
+
- I had to disable warnings on this because I wasn't sure what flags I needed to set in the feature or the experimental,
- also the lack of documentation definitely hurt, it took me a while to figure out I had to use feature 'class';. For some reason I thought
- the feature flag was corinna or something.
+ I had to disable warnings on this because I wasn't sure what flags I needed to set in the feature or the experimental,
+ also the lack of documentation definitely hurt, it took me a while to figure out I had to use feature 'class';. For some reason I thought
+ the feature flag was corinna or something.
-
+
CORINNA TIME : 564.411ms
-
+
- Moo is a "Minimalist Object Orientation" for Perl. It's what I've used the most, and it performs quite well for all of the
- features it gives you. However, there is definitely a cost, one that a lot of developers ignore. I've done two implementations here, one with
- Type::Tiny, which adds optional type-checking to attributes, and one without. The difference was quite compelling!
+ Moo is a "Minimalist Object Orientation" for Perl. It's what I've used the most, and it performs quite well for all of the
+ features it gives you. However, there is definitely a cost, one that a lot of developers ignore. I've done two implementations here, one with
+ Type::Tiny, which adds optional type-checking to attributes, and one without. The difference was quite compelling!
Type::Tiny:
-
+
package Action;
use Moo;
@@ -227,11 +291,11 @@ my $age = 12;
for ( 1 .. 1000000 ) {
my $user = Action->new( name => $name, age => $age )->execute();
}
-
+
With Type::Tiny:
-
+
package Action;
use Moo;
@@ -267,32 +331,32 @@ my $age = 12;
for ( 1 .. 1000000 ) {
my $user = Action->new( name => $name, age => $age )->execute();
}
-
+
- To my shock, Type::Tiny degrades performance by close to 300%. However, the more I looked into Type::Tiny, the more it made sense.
- Type::Tiny, to enforce types uses a lot of complex meta-programming, and ref checks, that add up to a ton of extra operations. Perhaps it would be
- worth using B::Concise or B::Deparse to see how many more operations this actually adds, but thats something for another day.
+ To my shock, Type::Tiny degrades performance by close to 300%. However, the more I looked into Type::Tiny, the more it made sense.
+ Type::Tiny, to enforce types uses a lot of complex meta-programming, and ref checks, that add up to a ton of extra operations. Perhaps it would be
+ worth using B::Concise or B::Deparse to see how many more operations this actually adds, but thats something for another day.
-
+
MOO TIME : 960.03ms
MOO + TYPES TIME : 2308.226ms
-
+
- Moose is a heavyweight, industrial purpose OO framework for Perl, at which Moo derives from. It is big, and heavy, and unfortunately,
- really slow for tasks like this. After starting this benchmark, I thought that the slowness was coming from loading the module between runs, but after timing the average load time,
- the time spent was negligble (31ms at most).
+ Moose is a heavyweight, industrial purpose OO framework for Perl, at which Moo derives from. It is big, and heavy, and unfortunately,
+ really slow for tasks like this. After starting this benchmark, I thought that the slowness was coming from loading the module between runs, but after timing the average load time,
+ the time spent was negligble (31ms at most).
- Like in the Moo benchmark, I did two implementations, one using Type::Tiny, and one not.
+ Like in the Moo benchmark, I did two implementations, one using Type::Tiny, and one not.
-
+
package Action;
use Moose;
@@ -325,11 +389,11 @@ my $age = 12;
for ( 1 .. 1000000 ) {
my $user = Action->new( name => $name, age => $age )->execute();
}
-
+
With Type::Tiny:
-
+
package Action;
use Moose;
@@ -365,29 +429,29 @@ my $age = 12;
for ( 1 .. 1000000 ) {
my $user = Action->new( name => $name, age => $age )->execute();
}
-
+
- Just like Moo with types, Type::Tiny hits the final Moose result by around 2000ms overall.
+ Just like Moo with types, Type::Tiny hits the final Moose result by around 2000ms overall.
-
+
MOOSE TIME : 11420.455ms
MOOSE + TYPES TIME : 13649.37ms
-
+
- I also did a quick Ruby implementation, since its a "true" Object-Oriented language, I assumed it would have - a lot of optimizations built-in for this sort of work, and based on its runtime, that seems to be the case. + I also did a quick Ruby implementation, since its a "true" Object-Oriented language, I assumed it would have + a lot of optimizations built-in for this sort of work, and based on its runtime, that seems to be the case.
- Also, note, this was on Ruby 3.1.2, not Ruby 3.3 with YJIT, so this could theoretically be faster. + Also, note, this was on Ruby 3.1.2, not Ruby 3.3 with YJIT, so this could theoretically be faster.
-
+
class Action
def initialize(age, name)
@age = age
@@ -404,23 +468,23 @@ name = 'Tim'
1000000.times do
user = Action.new(age, name).execute()
end
-
+
-
+
RUBY TIME : 167.627ms
-
+
- Finally, we have Raku, which unfortunately doesn't do too well. To make this a little more competitive, - I subtracted the run-time of MoarVM from the end-result. Though, I think Raku's implementation is the simplest to - understand in terms of langauge design, so it gets a +1 from me for that. + Finally, we have Raku, which unfortunately doesn't do too well. To make this a little more competitive, + I subtracted the run-time of MoarVM from the end-result. Though, I think Raku's implementation is the simplest to + understand in terms of langauge design, so it gets a +1 from me for that.
-
+
class Action {
has $.name;
has $.age;
@@ -436,39 +500,39 @@ for 1..1000000 -> $ {
my $user = Action.new(name => $name, age => $age).execute();
}
-
+
-
+
RAKU TIME : 801.947ms
-
+
- Object instantiation has a cost. A lot of people ignore this, especially when using heavyweight libraries like Moose.
- You can put this into practical terms simply, using basic Perl module OO you effectively double the number of objects of this type you can create and execute a
- method on given any time-frame compared to Moo without types, and twenty-two times more objects versus Moose without types.
- But these abstractions exist for reasons, and they are used for a reason.
+ Object instantiation has a cost. A lot of people ignore this, especially when using heavyweight libraries like Moose.
+ You can put this into practical terms simply, using basic Perl module OO you effectively double the number of objects of this type you can create and execute a
+ method on given any time-frame compared to Moo without types, and twenty-two times more objects versus Moose without types.
+ But these abstractions exist for reasons, and they are used for a reason.
- Moo and Moose have their places, especially in web-applications where the number of instantiations is low, and the expensive
- operations happen during side-effects like database operations.
- This also goes without mentioning some of the great features modules like Moo and Moose provide.
- It is valuable to know that if you need something to perform well, Moo and Moose
- may incur overhead you hadn't previously considered. This is compounded when enforcing types.
- For hot-code that will be hit repeatedly within a short amount of time, this can drastically effect performance, think of things like a landing page, or a search-bar API.
+ Moo and Moose have their places, especially in web-applications where the number of instantiations is low, and the expensive
+ operations happen during side-effects like database operations.
+ This also goes without mentioning some of the great features modules like Moo and Moose provide.
+ It is valuable to know that if you need something to perform well, Moo and Moose
+ may incur overhead you hadn't previously considered. This is compounded when enforcing types.
+ For hot-code that will be hit repeatedly within a short amount of time, this can drastically effect performance, think of things like a landing page, or a search-bar API.
- Corinna, is a very nice prospect, it offers a lot of great things from Moo and Moose but seems to
- exalt far less of a performance penalty. It will definitely be on my radar in the future, especially when the documentation improves.
+ Corinna, is a very nice prospect, it offers a lot of great things from Moo and Moose but seems to
+ exalt far less of a performance penalty. It will definitely be on my radar in the future, especially when the documentation improves.
- Another approach is to simply use functions, which of course will perform the best. But, it's hard to argue to do this unless performance is the number one goal, - especially on codebases that follow this pattern. + Another approach is to simply use functions, which of course will perform the best. But, it's hard to argue to do this unless performance is the number one goal, + especially on codebases that follow this pattern.