Hi everyone! How are you doing? It's me, Aaron (some people know me on the internet as Tenderlove)! I hope you're doing well. ❤️
Since we're coming up on the end of the year, I thought it might be a good idea to take a moment to reflect on cool stuff that's happened or is happening in the Ruby community right now. Actually, it wasn't my idea, one of my coworkers suggested I should write a blog post about cool stuff that's been happening, and I agreed it was a good idea. 😆
It’s no secret that Shopify loves Ruby—we even have a team (I’m on it!) specifically dedicated to improving Ruby and Rails for everyone. So let’s go over a few things that have been happening this year in the Ruby and Rails world, in and out of Shopify, then see if I can make some predictions about next year!
YJIT on ARM
The first thing I'd like to highlight is YJIT coming to the ARM platform. Over the past year the YJIT team rewrote the compiler in Rust. A JIT compiler is a pretty complicated piece of software and the C implementation was getting unwieldy, so they decided to rewrite the compiler in Rust! The Rust port is much more maintainable, and of course the new compiler generates better and faster machine code than the old compiler.
However, the speed benefit isn't the thing I want to highlight here. In addition to generating faster code, YJIT also gained the ability to generate machine code for ARM machines. Not only does this mean you can run YJIT on ARM machines in production, but you can also run it on your Apple Silicon computers! To me, this is really exciting because it means that we can potentially speed up development environments, and we also don't have to special case production environments. In other words,
ruby --yjit will work in development as well as production!
Variable Width Allocation is Default
In Ruby 3.1 and older (though I'm not actually sure how old), objects occupied a fixed width size of 40 bytes. Ruby uses an optimization on objects where references to instance variables are stored inside the object itself. Due to the way objects are laid out in memory, this means that Ruby could store up to three instance variable references “in-line” with the object itself. Any more references would require allocating an extra buffer and writing references to that extra buffer. The issue is that this extra buffer could be located in memory far away from where the original Ruby object was allocated, meaning that whenever Ruby had to look up an instance variable it would have to go to system memory rather than hitting one of the CPU caches.
The Variable Width Allocation project aimed to fix this problem and also move the codebase away from the idea that Objects can be one specific width. When an object is allocated with more instance variables than can fit in a particular slot, the system “learns” this fact, and adjusts accordingly. Subsequent instances of the same object will be allocated from a pool where the slot size is wide enough to accommodate the number of instance variables required from the last allocation.
Foo instance can't fit in the slot where it was allocated, but the system “learns”, and the second instance will be wider than the first instance. This allows us to embed all instance variables in the object and improve data locality in the system.
Object shapes is an optimization technique that's been added for Ruby 3.2. If this project is working well, then hopefully you won't notice it's there at all. 🤣
This project adds a “shape” to all objects in the system. The shape represents properties about the object, and the order in which the properties were set. Each time an instance variable is set on an object, it changes the object's shape. The id of the shape can then be used as a cache key for inline caches. The really cool thing about this technique is that the cache key is independent of the class of the object. Any object that sets the same instance variables in the same order can benefit from the shape. This is especially useful in cases where there are many subclasses of some other class (I'm looking at you Active Record!)
Ruby “remembers” where an instance variable should be written inside an inline cache. I don't want to get too into details, but before the Object Shapes project, the cache key was based on the class of the object. That means that even though
@a = 1 is executed for both instances A and B , the cache key wouldn't hit because
self had a different class. Since Object Shapes don't care about the class, only the properties and their order, that means this code can now hit on inline caches.
I am so excited about this project. Ruby LSP is an LSP server for Ruby. Just like the name says! 🤣
If you haven't played around with LSP servers, I really encourage it. They help with things like formatting your code, looking up documentation, jumping to function declarations, syntax highlighting, etc. Ruby LSP provides this type of functionality for Ruby and Rails applications. We've been working very hard on this project so that it will work well for developers at Shopify, and I hope that it will work well for everyone in the community as well! (If not, please let us know!!!)
If you check the supported features page, each of the supported features has a gif that demonstrates that particular feature. For example, the Formatting link shows an example of Ruby LSP fixing the formatting of Ruby code inside VSCode.
I am so excited about this project because I think it will be a boon for Ruby and Rails development productivity. There's nothing I love more than getting done with my job more quickly! 😆
Error Highlight is a gem that was originally written by Yusuke Endoh (also known as Mame, pronounced as “mah may”). When exceptions occurred in Ruby programs, the error output would show a stack trace that included line information, and sometimes there would be a “did you mean?” message as well.
Error Highlight enhances the error messaging even further and provides column information and ascii art showing you exactly where in the code the exception occurred.
Error Highlight provided the
^^^^^ ASCII art in the error message pointing out exactly where the error occurred. Maybe this example isn't too impressive, but have you ever written code to extract data from many levels of nested hashes from JSON data? I have. And whenever I mess up the hash key it's very hard to figure out which one is broken.
Take the following code for example:
There’s a typo in the
extract method, but it might be hard to see. With older versions of Ruby, the output would look like this:
With newer versions (3.1+) the output looks like this:
Now you can see exactly where the error occurred. This functionality isn't new this year, but what is new is that Yusuke was able to incorporate this information into Rails error pages.
Now if you get an exception while developing your Rails application, you'll be able to see exactly where the error occurred right on the error page! It's so cool!!!
Alright. We've made it to the predictions portion of the presentation, or the PPP for short. I have some ideas for predictions, but to be honest it's more of a wishlist.
Half of the projects I Error Highlighted (get it?) were performance and Ruby internals related, and the other half were about developer productivity. I think that Ruby performance is going to get better year over year, but that's not the thing I want to predict (I mean I guess it's technically a prediction since I think it will happen, but I don't have any proof!) I really want to talk about developer productivity and ergonomics. I think that the developer community, and programming languages as a whole are going through a revolution with regard to programmer ergonomics. We used to think that stack traces were good enough to debug our programs, but today that is no longer good enough. Today having good error messages, editor integration, and documentation are table stakes for any programming language.
As a language that is optimized for “Developer Happiness”, I think that Ruby and Rails are in a unique situation to provide even easier ways to work, better development environments, and more flexible ways to express your thoughts as code.
My prediction (read: hope) for next year is that our languages, frameworks, and development environments will become more interconnected.
They'll provide us with better suggestions, more help, and better context about our code. That way we can spend less time coding, and more time problem solving.
Thanks everyone! I hope you all have a wonderful and safe holiday season and a happy New Year!
More Ruby and Rails Stories from 2022
- Shopify and Open Source: A Mutually Beneficial Relationship
- Shopify Invests in Research for Ruby at Scale
- Code Ranges: A Deeper Look at Ruby Strings
- To Thread or Not to Thread: An In-Depth Look at Ruby’s Execution Models
- Finding Relationships Between Ruby’s Top 100 Packages and Their Dependencies
- Implementing Equality in Ruby
- RubyConf 2021: The Talks You Might Have Missed
- RailsConf 2022: 10 Shopify Tech Talks You Might Have Missed
- Making Open Source Safer for Everyone with Shopify’s Bug Bounty Program
- Caching Without Marshal Part 1: Marshal from the Inside Out
- Caching Without Marshal Part 2: The Path to MessagePack
Aaron Patterson is a Senior Staff Engineer on Shopify’s Ruby and Rails Infrastructure team, and Ruby on Rails Core Team member. On the internet, you may know him as Tenderlove. You can connect with him on GitHub and Mastodon.
Open source software plays a vital and integral part at Shopify. If being a part of an Engineering organization that’s committed to the support and stewardship of open source software sounds exciting to you, visit our Engineering career page to find out about our open positions and learn about Digital by Design.