Ruby 3.3.0

rubyJanuary 30, 2024Dotby Alkesh Ghorpade

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

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

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

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

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

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

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

Closing Remark

Could your team use some help with topics like this and others covered by ShakaCode's blog and open source? We specialize in optimizing Rails applications, especially those with advanced JavaScript frontends, like React. We can also help you optimize your CI processes with lower costs and faster, more reliable tests. Scraping web data and lowering infrastructure costs are two other areas of specialization. Feel free to reach out to ShakaCode's CEO, Justin Gordon, at [email protected] or schedule an appointment to discuss how ShakaCode can help your project!
Are you looking for a software development partner who can
develop modern, high-performance web apps and sites?
See what we've doneArrow right