All Ruby 3.0’s new features
Although December was a long time ago, the buzz after the new Ruby major release hasn’t passed yet. As in United Ideas, we constantly look for news from our beloved Ruby language and community, we prepared some informations concerning new features of Ruby 3.0.
The main complaint heard falsely from developers about Ruby is its performance. Some developers say that Ruby is extremely slow.
Recently, CSAIL MIT for National Earth Day, posted on their Facebook wall a comparison comparing the energy, memory, and time needed to execute a particular code unit in different languages.
And guess what? Ruby is almost the last one in each category. But is it true with Ruby 3 as well?
No, it’s not! Although the Ruby domain considers programmers’ convenience, with a new Ruby release, authors planned on speeding it up in two fields – CPU usage and memory usage.
Matz (Yukihiro Matsumoto), the author of a Ruby programming language, promised to make a new Ruby, three times faster than Ruby 2.
Ruby 3.0 improvements – CPU usage
The main improvements were made around the MJIT compiler. This compiler (JIT) was present in Ruby from version 2.7, but in version 3.0, the whole work was finally merged.
The MJIT compiler is different from a regular JIT because it uses a background thread and a Ruby to C compiler (we are talking about the most common implementation – CRuby).
This compiler assumes that when the method has been called a certain number of times (for Ruby 2.7 it was 10.000 times), it is marked as to be turned into a native code and put into the queue from which the thread pulls it compiles once at a time.
Below you can find an official comparison of Ruby JIT frames achieved using the NES emulator (optcarrot).
Ruby 3.0 improvements – Memory optimization
Then they optimized memory. I was achieved in Ruby 1.7 by object compaction. Even a compact (MRI) method is available in the GC module, which is doing this.
Compaction of objects means eliminating unused memory on the heap (or fragmentation) by moving other objects into unused space. Ruby 3.0 introduced the automatic auto compact mode (auto_compact method).
Another significant Ruby 3.0 improvement concerns the concurrency model. Previous Ruby versions used the infamous GIL model (Global Interpreter Lock).
The main drawback of this model was that it prevented developers from running the code in real parallel mode – only one thread was executing on the core (this may not be true for JRuby running obviously on the JVM).
The leading cause for this was to prevent thread-safety issues. Ruby 3.0 introduced, known for example from Erlang, an actor model using a primitive called Ractor. Each Ractor gets its own lock, and each of them can also hold potentially multiple threads.
Ractor holds its memory chunk and can exchange data with other Ractors using messages. Although it seems pretty advanced, in Ruby, it is pretty straightforward.
Below is the basic example of message passing. It is worth mentioning that the message passing mechanism happens on the Ractors objects themselves. Additionally, each Ractor can have a name, may receive message conditionally – receive_if – and more!
Ruby 3.0 features – Fiber scheduler
Another cool thing introduced in Ruby 3.0 was the Fiber scheduler. Fibers, sometimes called “lightweight threads/user-mode threads,” are different because they are controlled by the programmer and not the Ruby VM.
The reason behind this is that every fiber has its own stack. In Ruby 3.0 scheduler is set on the fiber using the set_scheduler method. You can find the scheduler structure in the image below. It is the Fiber::SchedulerInterface implementation.
Ruby 3.0 improvement – Ruby program type description
The last main feature introduced was support for Ruby program types description. You can achieve it via RBS (rbs gem), bundled with Ruby. A type analysis tool called TypeProf reads plain Ruby code and produces RBS formatted type definition. You can find a small example below.
Ruby 3.0 improvement – pattern matching
One of the last extensions worth mentioning is something tasty. It is well known for functioning in programming languages (Elixir 😉) – pattern matching. They introduced a find pattern in experimental pattern matching. You can find the example below.