Ruby 3.3.0
Ruby 3.3.0 was released on December 25, 2023! The much-awaited Ruby 3.3.0 has arrived, gracing us with new features, performance boosts, and developer delights.
Ruby 3.3.0 prioritizes performance and developer experience improvements while introducing fewer significant language than previous versions. Let's go over the changes added in Ruby 3.3.0.
Prism Parser
What is it?
Prism is a brand-new parser for Ruby. It is more error-tolerant and is used to replace the older Ripper parser. It's designed to be faster, more accurate, and easier to maintain. It plays a crucial role in how Ruby code is interpreted and executed.
Key features in Prism
-
Portable: Written in C99, it works across different platforms without dependencies. This can be advantageous for portability and avoiding potential compatibility issues with different versions of external dependencies.
-
Error tolerant: Gracefully handles errors, providing informative messages for better debugging. Prism utilizes a different approach to parsing than the traditional Ripper parser used in previous Ruby versions. This new approach aims to be more forgiving and tolerant of minor syntactic errors in the code.
-
Recursive descent: Prism employs a recursive descent parsing technique. This method breaks down the code into smaller, more manageable structures, analyzing each individually.
Benefits of using Prism parser
-
Performance:
Prism parses code significantly faster than the older Ripper parser, leading to faster startup times and overall performance improvements, especially for larger codebases. Its design enables opportunities for more targeted performance enhancements in the future.
-
Error handling:
Prism provides more informative and precise error messages, making debugging and troubleshooting errors in your code more straightforward and more efficient. It handles errors gracefully, often continuing to parse code even in the presence of errors, providing more context for troubleshooting.
-
Maintainability:
Prism's code is well-structured and easier to understand, simplifying the parser's maintenance, modification, and future development. This improved maintainability allows smoother updates and advancements in the parser's capabilities.
Prism offers a faster, more reliable, and maintainable parsing experience, paving the way for future Ruby advancements and a more efficient and enjoyable developer experience.
YJIT Performance Improvements
YJIT was introduced in Ruby 3.2.0. Ruby 3.3.0 significantly improves to YJIT performance, making it faster and more efficient than ever before.
Benchmarks show up to a 15% speedup over Ruby 3.3 interpreter and 13% over 3.2 YJIT on average. Specific benchmarks like Optcarrot reveal over 3x faster execution with YJIT enabled. Basecamp and Shopify, already testing Ruby 3.3 previews in production, have witnessed a significant 15% average response time gain, thanks to the revolutionary YJIT compiler.
Because YJIT is so efficient, Rails 7.2 has enabled YJIT by default if you run Ruby 3.3+. Please refer to this PR for more details.
Ruby 3.3.0 lets you take the reins on speed with RubyVM::YJIT.enable
.
Launching your app with --disable-yjit
or
--disable-rjit
can prioritise a quick boot.
Then,
RubyVM::YJIT.enable
flips the switch for a performance boost later,
maximizing responsiveness once your app settles in.
Pure Ruby JIT Compiler (RJIT)
Ruby 3.3 introduces an experimental pure-Ruby Just-in-Time (JIT) compiler named RJIT. It's specifically designed to explore the possibilities of JIT compilation within Ruby itself. It compiles Ruby code into machine code at runtime, aiming to boost performance.
It's an alternative to the existing YJIT compiler, offering a different approach and potential for future optimization.
Currently, RJIT supports x86-64 on Unix-like platforms. Unlike MJIT, it doesn't require a C compiler at runtime. It is still under active development and has yet to be recommended for production use.
RJIT is expected to continue evolving and become a viable alternative to YJIT in the future.
IRB
In Ruby 3.2,
IRB introduced commands such as debug
and
next
.
This was a significant improvement in Ruby 3.2.
With Ruby 3.3.0,
enhanced debugging is possible using the advanced irb:rdbg
integration.
The addition provides a seamless debugging experience comparable
to popular tools like pry-byebug.
You can leverage features like stack inspection,
variable evaluation,
and
breakpoints within the familiar IRB environment.
You can also customize the IRB dropdown UI using
Reline.
A new class,
Reline::Face
has been added to customize the IRB dropdown UI.
For more details,
please refer to this
link.
Apart from these changes,
additional features like pager support for ls
,
show_source
and
show_cmds
are added.
These commands now handle large outputs effectively,
offering cleaner display
and
navigation through IRB history
and
available commands.
The ls
and
show_source
commands are more informative.
These commands provide richer details about objects
and
methods,
facilitating a better understanding of your code in the console.
M:N Thread Scheduler
A new M:N thread scheduler has been added in Ruby 3.3.0.
M:N thread scheduling manages M Ruby threads on N native system threads. Typically, M is the number of ractors, and N is chosen to be the number of CPU cores available to maximize potential parallelism. This differs from the 1:1 model, where every Ruby thread requires its system thread, often leading to overhead and limitations on the number of concurrent threads.
M:N allows running more concurrent Ruby threads compared to 1:1, potentially improving application performance and responsiveness. By sharing system threads, individual Ruby threads have lower overhead, benefiting memory usage and context switching.
But M:N comes with a few challenges. Implementing and maintaining an M:N scheduler is more complex than the 1:1 model, requiring careful synchronization and context-switching handling. Existing libraries and code might not be fully compatible with an M:N scheduler, potentially requiring changes or adaptations.
Range
For a given range,
you can check if there is another overlap or not,
using the #overlap?
method.
For more details,
please refer to our previous blog post on
Ruby 3.3 introduces the Range
overlap? method.
a = (1..5)
b = (5..10)
a.overlap?(b)
=> true
a = (1..5)
b = (10..12)
a.overlap?(b)
=> false
(1..5).overlap?(1)
=> TypeError, argument must be Range
You can reverse the begin-less ranges using the reverse_each
method.
(..5).reverse_each.take(3)
=> [5, 4, 3]
MatchData#named_captures
The named_captures
method accepts symbolize_names: true
as a keyword argument.
This will automatically convert keys as symbols
instead of keys as strings (default).
full_name = "sam_example"
pattern = /(?<first_name>\w+)_(?<last_name>\w+)/
full_name.match(pattern).named_captures
{
"first_name" => "sam",
"last_name" => "example"
}
full_name.match(pattern).named_captures symbolize_names: true
{
first_name: "sam",
last_name: "example"
}
Conclusion
Under the hood, Ruby 3.3 boasts numerous optimizations. Faster and less frequent garbage collection, memory efficiency improvements, code execution optimizations, and YJIT advancements deliver a significant performance boost, often without requiring code changes.