From da242fe7bde8440358403629d4581dc36c4f54b1 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 8 Apr 2025 00:04:25 -0500 Subject: [PATCH] whoops --- .gitignore | 4 - posts/2024.html | 59 +++ posts/become-an-engineer.html | 81 +++ posts/dancer2-controllers.html | 72 +++ posts/docker-mod-perl.html | 144 +++++ posts/emacs.html | 53 ++ posts/in-defense-of-global-variables.html | 102 ++++ posts/jpa-hibernate-stupid-performance.html | 56 ++ posts/maybe-escaping-evil.html | 50 ++ posts/niche-software.html | 44 ++ posts/perl-oo-action-benchmark.html | 558 ++++++++++++++++++++ posts/trying-lite-xl.html | 17 + 12 files changed, 1236 insertions(+), 4 deletions(-) create mode 100644 posts/2024.html create mode 100644 posts/become-an-engineer.html create mode 100644 posts/dancer2-controllers.html create mode 100644 posts/docker-mod-perl.html create mode 100644 posts/emacs.html create mode 100644 posts/in-defense-of-global-variables.html create mode 100644 posts/jpa-hibernate-stupid-performance.html create mode 100644 posts/maybe-escaping-evil.html create mode 100644 posts/niche-software.html create mode 100644 posts/perl-oo-action-benchmark.html create mode 100644 posts/trying-lite-xl.html diff --git a/.gitignore b/.gitignore index a279d1f..cab025d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,10 +5,6 @@ esy.lock/ *.db *.pem *~ -posts/* -posts/*.blog -posts/*.post -\#* .DS_Store bookshelf.txt site.db* diff --git a/posts/2024.html b/posts/2024.html new file mode 100644 index 0000000..a743ffc --- /dev/null +++ b/posts/2024.html @@ -0,0 +1,59 @@ + + + + 2024 + + + + + << back +

2024

+

+ 2023 was a good year, but now it's 2024. Here's some of my technical plans for 2024. +

+

+ NOTE: These are in no particular order. +

+

Technology I plan to use in 2024

+ +

Technology I want to learn more about in 2024

+ +

Technology I might learn in 2024

+ +

Technology I won't learn in 2024

+

+ It's not that I hate the following, it's just that they don't really interest me, + or the people behind them do not seem to value people using them (ala Rust). +

+ +

Have a safe and fun 2024.

+ + diff --git a/posts/become-an-engineer.html b/posts/become-an-engineer.html new file mode 100644 index 0000000..a8ce9f9 --- /dev/null +++ b/posts/become-an-engineer.html @@ -0,0 +1,81 @@ + + + + Become a software engineer + + + + + << back +

Become an Engineer

+

+ This learning track assumes you have a computer built after 2004, with + Debian Linux, FreeBSD or OpenBSD installed. (Windows simply won't do.) +

+

Start here (read both and do all of the exercises)

+ +

Getting deeper (read all 3, do all exercises)

+ +

Low level learning

+ +

Category theory

+ +

High(er) Level Languages (pick at least 2)

+ +

+ This is the way to becoming a truly great software engineer. I've read all of these books, and I can say that they all manifest within + me a strong sense of confidence, and the ability to adapt to any situation that might come my way in computer science. +

+

+ You may be reading this list, thinking... what about Java or JavaScript??? Now, I don't think these languages are bad per-se, but I do think + they have the ability to destroy your life. Once you've used Java or JavaScript, there is a good chance you will become closed off from + the rest of the computing world, falling into a world of patterns and objects. You may also find that languages like these have the adverse + effect of having incredibly low skill-ceilings, which compounds the idea of you becoming stuck. A low ceiling, and a low floor, is a prison, not + a tool to conjure the spirits of the computer. +

+ + diff --git a/posts/dancer2-controllers.html b/posts/dancer2-controllers.html new file mode 100644 index 0000000..c5739a6 --- /dev/null +++ b/posts/dancer2-controllers.html @@ -0,0 +1,72 @@ + + + + Dancer2::Controllers + + + + + + << Back +

Dancer2::Controllers and Perl Attributes

+

Published February 26th, 2024.

+

+ Recently I created Dancer2::Controllers, a simple + Moose based approach for defining routes in Dancer2. I built this as an attempt to add Spring-Boot esq annotations to + Dancer2. +

+

The approach

+

+ Perl has a little know feature called "Attributes", which work as some extra arbitrary data appended to + the various reftypes in Perl. When you append an Attribute, Perl looks for the MODIFY_<reftype>_ATTRIBUTES in the current + namespace, and uses it to decide if that attribute is allowed, and lets you execute some arbitrary code as well. Perl attributes can also take "arguments". + These "arguments" actually are just part of the Attribute, and you need to parse the entire Attribute to get the arguments. +

+

+ Since Perl Attributes are the closest thing I could find to annotations, I decided a generic Route attribute would be nice. + With a Perly looking argument pattern: Route(get => /foo), this feels a Perl version of Spring-Boot's + @RequestMapping(value = "/foo", method = GET), + which is exactly what I was looking for. +

+

Speed Bumps

+

+ Dancer2 is built on-top of Moo, unfortunately I couldn't find a nice way to handle Attributes using Moo, and inheritance. So, I settled for adding + Moose and the wonderfully helpful MooseX::MethodAttributes dist, which drastically simplified the work I needed to do. +

+

In action

+

The following is an example of how to use Dancer2::Controllers

: +
+            
+package My::Controller;
+
+use Moose;
+
+BEGIN { extends 'Dancer2::Controllers::Controller' }
+
+sub home_page : Route(get => /home) {
+    my $app = shift;
+    my $user_name = $app->session('user');
+    return "Welcome back $user_name!";
+}
+
+sub home_page : Route(post => /login) {
+    my $app = shift;
+    my $password = $app->body_parameters->get('password');
+    return "Here is your password: $password";
+}
+
+1;
+
+package main;
+
+use Dancer2;
+use Dancer2::Controllers;
+
+controllers([ 'My::Controller' ]);
+
+dance;
+            
+        
+

Thanks for reading :)

+ + diff --git a/posts/docker-mod-perl.html b/posts/docker-mod-perl.html new file mode 100644 index 0000000..599f671 --- /dev/null +++ b/posts/docker-mod-perl.html @@ -0,0 +1,144 @@ + + + Running mod_perl with Mojolicious in Docker + + + + + + +
+ << back +

Running mod_perl with Mojolicious in Docker

+

+ I've been working under an old Apache mod_perl setup. I wanted to use Mojolicious for some new services, + but I needed to stay within mod_perl, so I decided to go all the way and Dockerize it. After scouring the mod_perl documentation, + Plack + Mojolicious configs, and finding an image, I hit the ground running and documented my steps. +

+ + mod_perl banner + +

I love old banners :)

+

Generate your config

+

Thanks to motemen for some nicely pre-configured + mod_perl and Apache2 images. We're targetting Perl v5.36, and Apache 2.4.58 here. + We'll also add the include statement for our VirtualHost configuration file that we'll make later. +

+
+docker run --rm motemen/mod_perl:5.36.0-2.4.58 cat /usr/local/apache2/conf/httpd.conf > httpd.conf
+echo 'Include /usr/local/apache2/conf/mojolicious.conf' >> httpd.conf
+                
+            
+

+ This will leave you with a httpd.conf that will get picked up by Apache when we run it under docker. +

+

Enable Prefork MPM instead of Event MPM

+

+ I couldn't get Apache to run without disabling Event MPM and enabling Prefork MPM. +

+

In httpd.conf:

+
+                
+#LoadModule mpm_event_module modules/mod_mpm_event.so
+LoadModule mpm_prefork_module modules/mod_mpm_prefork.so
+                
+            
+

Write your application

+

+ All you need to do is write a Mojo application. In this case, I'm using Mojolicious::Lite but this can + be done with the whole MVC setup easily as well. Just make sure you have a sub that returns your app so you can + pass it to Plack::Builder. +

+

In lib/App.pm:

+
+                
+package App;
+use 5.036;
+use Mojolicious::Lite -signatures;
+
+sub get_app { app; }
+
+get '/' => sub {
+    shift->render( text => "Hello World!" );
+};
+
+1;
+                
+

Write your Plack shim

+

+ Here we write a simple .psgi file that + will use our App library and Plack::Builder to + create a PSGI application for Plack::Handler::Apache2 to pickup. +

+

In app.psgi:

+
+use 5.036;
+
+use lib 'lib';
+use App;
+use Plack::Builder;
+
+builder {
+    App::get_app->start;
+};
+                
+            
+

Write your mod_perl Apache config

+

+ Very straight-forward, just tell Apache where to find our script, and what handler to use. +

+

In mojolicious.conf:

+
+                
+LoadModule perl_module modules/mod_perl.so
+
+<VirtualHost *:80>
+<Location />
+SetHandler perl-script
+PerlResponseHandler Plack::Handler::Apache2
+PerlSetVar psgi_app /app.psgi
+</Location>
+</VirtualHost>
+                
+            
+ +

Write your Dockerfile

+

+ Now all thats left is making a Dockerfile. + We'll use one of motemen's images, + and copy all of the pre-reqs over, install some libraries, you know, Docker stuff. +

+

In Dockerfile:

+
+                
+FROM motemen/mod_perl:5.36.0-2.4.58-2.0.13
+COPY app.psgi /app.psgi
+COPY lib /usr/local/apache2/lib
+COPY httpd.conf /usr/local/apache2/conf/httpd.conf
+COPY mojolicious.conf /usr/local/apache2/conf/mojolicious.conf
+RUN apt-get update -y && apt-get install -y wget make build-essential
+RUN cpan -iT Plack Mojolicious
+EXPOSE 80
+CMD ["httpd-foreground"]
+                
+            
+ +

Run with Docker

+

Now we can make it go!

+
+$ docker build . -t mod-perl
+$ docker run -p 80:80 mod-perl
+$ curl http://localhost:80/
+
+"Hello World!"
+                
+            
+

Running with auto-reloading (for development)

+

You can make this auto-reload if you use a docker volume for lib/ against /usr/local/apache2/lib/

+

An example?

+

+ An example is available on my github. +

+
+ + diff --git a/posts/emacs.html b/posts/emacs.html new file mode 100644 index 0000000..4947be0 --- /dev/null +++ b/posts/emacs.html @@ -0,0 +1,53 @@ + + + + Emacs + + + + + << back +

Why Emacs?

+

+ I reccommend GNU Emacs (not Spacemacs, or Doom Emacs) to everyone. If you're just starting to program + then you're at an even bigger advantage if you pickup Emacs early. If you're a battle hardened developer + you should still be using Emacs, though it may be harder to pickup, assuming you've been using modern "IDE's" + for most of your career. I also recommend Emacs to pretty much anyone that processes text often. +

+

+ Emacs provides the perfect balance of customization, to effectiveness of any editor. I think that it's foolish + to use tools that don't let you mold them to your idea of how to use them. Software tools aren't like traditional + tools like Hammers and Pipe-wrenches, and it's foolish to use them in this way. With Emacs a Secretary, Software Engineer (obviously), + Math Student, Professor, Novelist, etc, can all find ways to implement Emacs into their day-to-day. + Emacs is a tool for empowering people. It's not just a text-processor (though it's really good at that), it's also a journal, + a lisp interpreter, and really, anything you want. +

+

For Software

+

+ I'm going to focus on Software Developers for the rest of this, seeing as it's who I can relate too most. + Software Developers often get stuck in ruts, I truly think Emacs is the tool to pull you out and keep you out + of said ruts. Emacs will kick you in the rear, and make you cry, and force you to work in a subpar fashion for a long time, + the GNU is not an easy beast to tame. But once you've put in the work, and elevated yourself upon it's reigns, the power you + attain is so incredibly wizard-like that you may very well consider wearing a pointed hat, and robes, at work. +

+

+ Currently, I use Emacs for everything at work. Compiling, code editing, file-system stuff, docker, git, documentation, etc. + I've used other editors and nothing, comes even close to Emacs. Vim users will probably recoil, but Vim makes you context + switch so often between the terminal and your editor that I really think it doesn't even belong in the same category as Emacs. + What I would recommend to the Emacs novice, is to focus on setting up your workflows 1 flow at a time. For instance, say you want + to work on Perl code. Setup all the Perl related stuff in one-shot and don't worry about making your configuration super ergonomic + and extensible. Just make it work for Perl. Then say, you get a job in Scala, do the same thing, but refactor your configuration + to enable both Perl and Scala nicely, rinse and repeat until your hands bleed. That's really all there is to it, as you find things + you want to improve, improve them one-by-one. Don't rush in and expect to setup a perfect IDE for every language and every workflow. + Emacs really is what you make of it, but you can't really know what to make of it, until you identify the problems you want to solve. +

+

+ A good way to think of Emacs, is, it's the instrument equivalent of text-processors. +

+

Links:

+ + + diff --git a/posts/in-defense-of-global-variables.html b/posts/in-defense-of-global-variables.html new file mode 100644 index 0000000..8891a59 --- /dev/null +++ b/posts/in-defense-of-global-variables.html @@ -0,0 +1,102 @@ + + + + How to Properly Use Global Variables + + + + << back +

How to Properly Use Global Variables

+

+ Global variables, and global state in general have been under fire for as long as I can remember. + The argument is, you couple, and introduce unpredictability through managing global variables + since "anyone can update them". This makes sense in a cosmological world, filled with people + who WANT to write bad code. It's not the global variables that make code bad, it's how they're used. + We've spent many years inventing abstraction after abstraction, that could just be boiled down to global variables, + and global constants. In the end, applications will always have global state, why make it so dang difficult? +

+

Simplicity, and avoiding traps

+

+ Globals are simple, so simple, that even the thought of using them might turn people off. It's hard to accept + that the simplest answer is always the best one, but it almost always is. 99% of the code I run into, is complex for the sake of "reusability", "readability", or + "scalability", but in the end none of these come to pass. You'll see interfaces, and abstract classes defined for single implementations, + horribly generic code written in obscure places, and a mess of a file structure with hundreds of files. Do you really need a generic interface for + each of your database tables, or what about a "service" for each "domain"? In reality, the most readable, resuable, and scalable code, is the code that + compiles well, and avoids mentally expensive abstraction. If I have to walk through the inheritence graph to figure out what something is doing, it's already neither + scalable, or readable. +

+

+ You'll often see global variables hidden behind some "global context" like abstraction. This is down right deplorable, instead, hide your globals behind the simplest + abstraction on the market, the function. GetDatabaseHandler() is much simpler to reason about than GlobalContextHolder.getSingleton().getContext("database"). + These abstractions also, are prone to error, for instance if you use a hash-table to lookup values with a string key, what happens if I miss type the string? The compiler + won't catch that for you, and you'd be adding a lot of extra instructions just to access some state somewhere (which at its fastest is a pointer dereference). Another common way to do this, + is to pass a class object, .class in Java. However, this is even worse. Typically, a language will use reflection which or runtime type checks which can be extraordinarily slow. + To compound this, you also have no gurantees on the existence of the values, for instance, if you want a DatabaseHandler, and you make the request for it, you have no idea + if that is actually the right name or class until runtime. Contrast that with a function that will be picked up by your LSP or produce a compile-time error message. + Global contexts, and contexts in general are a huge mess of slow, hard to optimize code, you should avoid them at all costs. +

+

What to do instead

+

+ As mentioned earlier, global state should exist one layer under a function. There is absolutely nothing wrong with running a database.InitializeDatabase() function, to + initialize your globals at startup, or having some sort of lazy-init built into your global accessor function. Keeping things simple is how you keep them readable, and scalable. + What's funny, is that's pretty much it. +

+

+ Another thing I recommend is not accessing your globals inside of your functions, instead, pass them as arguments. This is just good-practice in general, your functions should + do their best to be as pure as possible. This also makes your code truly re-usable and adaptable, since you may have multiple different global variables with the same types. + This also makes your code easy to test, since it's not relying on any outside values existing or not. +

+

Examples

+

+ Let's compare some code. In the first example, we'll do the traditional GlobalContextHolder OOP style, + where we literally do our best to make it look like we're not working on global variables. In the second example, we allow our globals to exist, + but instead of accessing them in a foolish way, we accept them as parameters. +

+

Example 1:

+
+            
+public void saveUser(User u) {
+    // Where did this guy come from? Idk, it's probably defined in some XML file somewhere :)
+    DatabaseHandler dbh = GlobalContextHolder.getContext("database");
+    dbh.table("users").save(u);
+}
+
+// Main.java
+public static void main(String[] args) {
+    User user = new User("Bob", 23);
+    saveUser(user);
+}
+            
+        
+

Example 2:

+
+            
+// Database.java
+private static DatabaseHandler DBH;
+
+public static DatabaseHandler getDatabase() {
+    if (DBH == null) {
+        DBH = DatabaseHandler.newConnection("mysql://foo@bar:foo.com:3306");
+    }
+
+    return DBH;
+}
+
+// Main.java
+public static void saveUser(User u, DatabaseHandler dbh) {
+    dbh.table("users").save(u);
+}
+
+public static void main(String[] args) {
+    Database.initialize();
+    User user = new User("Bob", 23);
+    saveUser(user, Database.get());
+}
+            
+        
+

+ Overall, this drastically reduces the mental overhead of the system, allowing for simple unit-testing (if you want), and + a simplistic approach to scaling, since you don't have to worry about costly abstractions that don't actually simplify anything. +

+ + diff --git a/posts/jpa-hibernate-stupid-performance.html b/posts/jpa-hibernate-stupid-performance.html new file mode 100644 index 0000000..631dc02 --- /dev/null +++ b/posts/jpa-hibernate-stupid-performance.html @@ -0,0 +1,56 @@ + + + + Achieving Stupid Performance with JPA and Hibernate + + + + << Back +

JPA is slow, lets make it stupid fast

+

+ JPA is not performant. Mainly because of things like batching, and this incessant idea that + querying relationships lazily is somehow fast, its literally just deferring the work, which makes sense + if you potentially don't want to use the data, but most people want that data anyways, or serialize it. +

+

+ I experienced this recently with a massive data set, this database was 1 core table of 4 million rows, followed by multiple child One-To-Many table + of close to 10 million rows, and a single One-to-One with ~4 million rows as well. To use the service built on top of this massive data, you had to search + with JPA search specifications, which turns out also to be insanely slow. The benchmark starts us at 15 or so minutes, which is obviously unacceptable. +

+

Time to go fast

+

+ By enabling logging.level.org.hibernate.SQL=DEBUG and logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE + you're able to see the ridiculous number queries JPA is executing, they're un-countable, there's so many!! How can we fix this? +

+

+ If you're thinking of using @Batch(100), it could work, only issue is JPA will batch each relationship, so each of our tables will still require + n/100 calls where n is the number of rows with relationships. So theoretically a 100x speed bump right? Well, it's actually more + like a 33x performance bump, since you still have the overhead of making the database connections, and need to get a lock every time you select. Doing this we moved to around + 3-5 minutes depending on load per large call. This however, was still unacceptable. +

+

+ What if instead we just forced JPA to use OUTER JOIN? Isn't that exactly what the ANSI SQL wizards would tell you to do? + Obviously, this is the solution, one single connection, 1 lock, and let the B-Tree optimizations inside of the database handle all of this ugliness for us. + To do this you need to use something called @EntityGraph + on your JPA repository, what this does is forces JPA to use a OUTER JOIN + to join relationships. This even works on nested entities, say I have Entity A with a child B which also has a One-to-Many on C, all you need to do + is add the relationship to the entity graph with the @NamedEntityGraph annotation, then provide it with a few nice args like name, + and attributeNodes which will point to your sub entity. Then finally on your JPA repository call you stitch the entity graph back together. +

+
+            
+@Repository
+public interface ARepository extends JpaRepository<A, UUID>, JpaSpecificationExecutor<A> {
+    @Override
+    @EntityGraph(attributePaths = { "bs", "B.cs" }) // Where B's are children of A's, and C's are children of B's.
+    Page<A> findAll(@Nullable Specification<A> specification, Pageable pageable);
+}
+            
+        
+

+ After applying these, the query time went down from 15 minutes to ... *drum-roll* ... 3 seconds. So please, for the love of all that is computationally efficient, force your JPA + to use OUTER JOIN, or even roll your own SQL, ORM's really are one of the biggest trade-offs, and they really can bite you. This is why tend to + stay in the DBI universe when I write Perl. It's funny shocking Ruby on Rails and Java developers with my ~15ms queries :D +

+ + diff --git a/posts/maybe-escaping-evil.html b/posts/maybe-escaping-evil.html new file mode 100644 index 0000000..48920c8 --- /dev/null +++ b/posts/maybe-escaping-evil.html @@ -0,0 +1,50 @@ + + + + + Escaping evil-mode, maybe? + + + + + + << Back +

Escaping evil-mode

+

+ I've been using evil-mode for almost 4 years at this point, and Vim keybinds even longer. + However, I think it's almost time for me to change. In 2022 I spent the later half of the year just on default, vanilla emacs keybinds. + This was really enjoyable and let me learn more about how the puritans do Emacs, but in general, + I just felt a lot slower. So I came back to evil-mode, and have been using it daily ever since. + But with some recent annoyances in Evil, and having to almost double my config size to use Evil integrations with other packages, + I've been thinking of using Vanilla again, or exploring some of the other modal plugins like: meow, + Xah-Fly-Keys, or god-mode (don't love the name of this one :P). + So, to the disdain of my productivity, I am going to try to use each of these for a week at a time, and report back each week with an update. +

+

The test

+

+ I will be comparing each mode on the following all scored out of 10: +

+ +

evil-mode's rating

+

+ Here is my review of evil-mode: +

+ +

+ Overall I do like evil-mode, and i'm very productive with it, but I find that a lot of little issues pop-up on a day to day basis + that could be ironed out. Things like not having re-do automatically working. At the end of the day I use Emacs for both, speed, but also, + for extensability, something that I think evil-mode restricts. +

+ + diff --git a/posts/niche-software.html b/posts/niche-software.html new file mode 100644 index 0000000..c605bd5 --- /dev/null +++ b/posts/niche-software.html @@ -0,0 +1,44 @@ + + + + Using Niche Programming Languages + + + + + << Back +

Published March 7th, 2024

+

Using Niche Programming Languages

+

+ Earlier this week, + Bleacher Report + was bought out and forced to re-write their Elixir/Erlang backend in something "more-mainstream" by the + buying party (Warner), this is a HUGE mistake, and an example of how main-stream thought has eroded the foundations of software engineering. Bleacher Report had used + Elixir/Erlang since at least 2017, + when their case-study of going from 150 servers to 5 via converting their Ruby code to Elixir/Erlang was published. + All of this resliency, and engineering effort is now going to be rewritten in none other than JavaScript, which of course will be a tremedous effort. +

+

+ So why did they do it? Well, they had lay-offs, and then they needed to re-fill the seats they laid off a few years later. Instead of leveraging this "gold card" + that is a niche language, Warner decides that it will be too hard to fill N seats. Which is also another issue in the software world, the "butts in seats typing" + phalacy that more will get done. Anyways, what Warner could have done, is use this fantastical power bestowed by a niche language, and got the best developers + in that niche technology. If they hired 15 Node devs for 100k a year each, they could afford 5 Erlang masterminds for 300k a year each (and then you wouldn't need a + rewrite!) The other option is to hire a few Erlang devs and have them train other devs which could be an effective cost-cutting measure as well, without + having to rewrite your codebase. +

+

+ I've been asked many times by developers, "should I learn X, it's so niche but it looks cool", and to every single one of those questions, I shout "YES!!!". + Learning is ALWAYS good. But also, if you get really good at these niche technologies, you can become a SME (subject matter expert), and get hired + by a business looking for people like you. Jane Street is notorious for using OCaml for literally everything. Yaron Minsky, mentions at Jane Street states + that while there aren't many OCaml developers, the small set that does exist is typically really good, and a grade above the usual Python, Java, JavaScript developer + (you'll choke on your coffee when you read Jane Streets developer salaries as well). +

+

+ When starting a new project I think you should consider using a niche language. + You learn a lot, but also when it comes time to scale your idea, and you need to hire a partner or two, + you'll be greatful you chose it, when extremely talented and passionate individuals send you their resume solely because you chose a backend written in Haskell, Scala, + Perl, OCaml, Elixir, Clojure, Lua, etc. This is what happened for NoRedInk, after writing their original MVP in Ruby, they ended up using Haskell and Elm for new + services, and now, they're one of the most well known Functional Programming shops, and they've managed to hire some brilliant people, like the creator of Elm himself! +

+ + diff --git a/posts/perl-oo-action-benchmark.html b/posts/perl-oo-action-benchmark.html new file mode 100644 index 0000000..213a658 --- /dev/null +++ b/posts/perl-oo-action-benchmark.html @@ -0,0 +1,558 @@ + + + + + Perl OO benchmarking + + + + << back +

Perl OO benchmarking

+

+ 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). +

+

The benchmark

+

+ 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: +

+
+            
+my $name = 'Tim';
+my $age  = 12;
+for ( 1 .. 1000000 ) {
+    my $user = Action->new( name => $name, age => $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 benchmark was performed on an Intel i7 12700KF, using DDR5 memory. +

+

Summary Table

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LanguagePerformance
+ Perl Module OO + + 454.627ms +
+ Perl Module OO + Types + + 505.276ms +
Corinna564.411ms
+ Moo + + 960.03ms +
+ Moo + Types + + 2308.226ms +
+ Moose + + 11420.455ms +
Moose + Immutable1182.109ms
+ Moose + Types + + 13649.37ms +
Ruby167.627ms
Raku (MoarVM start-up subtracted)801.947ms
+

Times and implementations

+

Perl

+

Module OO

+

+ Using the traditional Perl approach to OO using ref's and modules, is by far the fastest. +

+
Code:
+
+            
+package Action;
+
+use strict;
+use warnings;
+
+sub execute {
+    my $self = shift;
+    return +{ name => $self->{name}, age => $self->{age} };
+}
+
+sub new {
+    my ( $class, $name, $age ) = @_;
+    my $self = { name => $name, age => $age };
+    return bless $self, $class;
+}
+
+1;
+
+package main;
+
+use strict;
+use warnings;
+
+my $name = 'Tim';
+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. +

+
Time:
+
+            
+PERL TIME : 454.627ms
+            
+        
+

Module OO with some type checks

+

+ 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;
+use warnings;
+use Scalar::Util qw(looks_like_number);
+
+sub execute {
+    my $self = shift;
+    return +{ name => $self->{name}, age => $self->{age} };
+}
+
+sub new {
+    my ( $class, $name, $age ) = @_;
+
+    if ( !defined $name || !defined $age ) {
+        die 'name and age, should be defined.';
+    }
+
+    if ( !looks_like_number($age) ) {
+        die 'age should be a number.';
+    }
+
+    my $self = { name => $name, age => $age };
+    return bless $self, $class;
+}
+
+1;
+
+package main;
+
+use strict;
+use warnings;
+
+my $name = 'Tim';
+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
+            
+        
+
Code:
+

Corinna

+

+ 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;
+
+class Action {
+    field $name : param;
+    field $age : param;
+
+    method execute () {
+        return +{ name => $name, age => $age };
+    }
+}
+
+my $name = 'Tim';
+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. +

+
Time:
+
+            
+CORINNA TIME : 564.411ms
+            
+        
+

Moo

+

+ 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! +

+
Code:
+ Without Type::Tiny: +
+            
+package Action;
+
+use Moo;
+use namespace::clean;
+
+has name => (
+    is       => 'ro',
+    required => 1
+);
+
+has age => (
+    is       => 'ro',
+    required => 1
+);
+
+sub execute {
+    my $self = shift;
+    return +{ name => $self->name, age => $self->age };
+}
+
+1;
+
+package main;
+
+use strict;
+use warnings;
+
+my $name = 'Tim';
+my $age  = 12;
+for ( 1 .. 1000000 ) {
+    my $user = Action->new( name => $name, age => $age )->execute();
+}
+            
+        
+ With Type::Tiny: +
+            
+package Action;
+
+use Moo;
+use Types::Standard qw(Int Str);
+use namespace::clean;
+
+has name => (
+    is       => 'ro',
+    isa      => Str,
+    required => 1
+);
+
+has age => (
+    is       => 'ro',
+    isa      => Int,
+    required => 1
+);
+
+sub execute {
+    my $self = shift;
+    return +{ name => $self->name, age => $self->age };
+}
+
+1;
+
+package main;
+
+use strict;
+use warnings;
+
+my $name = 'Tim';
+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. +

+
Time:
+
+            
+MOO TIME         : 960.03ms
+MOO + TYPES TIME : 2308.226ms
+            
+        
+

Moose

+

+ 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. +

+
Code:
+
+            
+package Action;
+
+use Moose;
+use namespace::clean;
+
+has name => (
+    is       => 'ro',
+    required => 1
+);
+
+has age => (
+    is       => 'ro',
+    required => 1
+);
+
+sub execute {
+    my $self = shift;
+    return +{ name => $self->name, age => $self->age };
+}
+
+1;
+
+package main;
+
+use strict;
+use warnings;
+
+my $name = 'Tim';
+my $age  = 12;
+for ( 1 .. 1000000 ) {
+    my $user = Action->new( name => $name, age => $age )->execute();
+}
+            
+        
+ With Type::Tiny: +
+            
+package Action;
+
+use Moose;
+use Types::Standard qw(Int Str);
+use namespace::clean;
+
+has name => (
+    is       => 'ro',
+    isa      => Str,
+    required => 1
+);
+
+has age => (
+    is       => 'ro',
+    isa      => Int,
+    required => 1
+);
+
+sub execute {
+    my $self = shift;
+    return +{ name => $self->name, age => $self->age };
+}
+
+1;
+
+package main;
+
+use strict;
+use warnings;
+
+my $name = 'Tim';
+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. +

+

+ Amendment: After publishing my original findings I was alerted to a setting you could apply to Moose classes that make them immutable. + Immutable often means faster in-terms of creation but slower in terms of manipulation, but since this workload spends most of its time creating, and none of its time editing, + this produced an incredible 10x speed improvement. +

+
Immutable trick for Moose
+
+            
+__PACKAGE__->meta->make_immutable;
+            
+        
+
Time:
+
+            
+MOOSE TIME         : 11420.455ms
+MOOSE + TYPES TIME : 13649.37ms
+MOOSE IMMU TIME    : 1182.109ms
+            
+        
+

Ruby

+

+ 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. +

+
Code:
+
+            
+class Action
+  def initialize(age, name)
+    @age = age
+    @name = name
+  end
+
+  def execute
+    { name: @name, age: @age }
+  end
+end
+
+age = 12
+name = 'Tim'
+1000000.times do
+  user = Action.new(age, name).execute()
+end
+            
+        
+
Time:
+
+            
+RUBY TIME : 167.627ms
+            
+        
+

Raku (formerly Perl 6)

+

+ 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. +

+
Code:
+
+            
+class Action {
+    has $.name;
+    has $.age;
+
+    method execute {
+        return %(name => $!name, age => $!age);
+    }
+}
+
+my $name = 'Tim';
+my $age = 12;
+for 1..1000000 -> $ {
+    my $user = Action.new(name => $name, age => $age).execute();
+}
+
+            
+        
+
Time (with MoarVM startup time removed):
+
+            
+RAKU TIME : 801.947ms
+            
+        
+

So what?

+

+ 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. +

+

+ 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. +

+
+ You can view the Source code. +
+ + diff --git a/posts/trying-lite-xl.html b/posts/trying-lite-xl.html new file mode 100644 index 0000000..7edc2f3 --- /dev/null +++ b/posts/trying-lite-xl.html @@ -0,0 +1,17 @@ + + + + + Using LiteXL! + + + + + + << back +

Trying Lite-XL

+

+ +

+ +