00:00:00.299
foreign
00:00:13.099
I'm really excited for Ruby company and I'm thrilled to be a part of it. My name is John Hawthorn, my pronouns are he/him, and I'm on the Ruby 14 release support team. I'm a staff software engineer on the Ruby architecture team at Game Mode. Largely, I love working on performance and related topics.
00:00:18.000
You can find me online at @jhawthorn on platforms like GitHub and Twitter, if Twitter is still online. I've also been enjoying Mastodon on ruby.social, so feel free to pick that up if you haven't already.
00:00:51.480
This talk is about the `is_a?` method, which you can call on any object to determine whether that object is of a specific class or module. The technical term usually used is whether it implements a class or module. Essentially, it checks if there is an exact match or if it checks the inheritance chain.
00:01:09.659
Let's look at a few quick examples on the same page: if we ask if the string 'foo' is a string, the answer is yes. If we ask if the integer 1 is a string, the answer is no. If we ask if the integer 1 is an integer, the answer is yes, which is true. However, since an integer is also a numeric, the `is_a?` method checks the class ancestry, confirming that integer is indeed a subclass of numeric.
00:01:43.439
I realize these examples might seem ridiculous, similar to that outdated meme where a guy asks whether a butterfly is a pigeon. In this case, we have our Ruby version, which is quite fitting. People intuitively understand class relationships, so it's odd that this could be a performance bottleneck.
00:02:08.759
However, the Ruby VM, like any computer, needs a strict definition of how to determine these relationships. While researching this, I found out that the `is_a?` method has efficiently been applied in other programming environments.
00:02:30.620
There are other methods that accomplish similar functionalities, such as the triple equals method (`===`), case statements, and rescue statements, all of which utilize a class inheritance check when called. These methods are frequently used, and because of their prevalence, I've found that they collectively account for about two percent of the total Ruby runtime. This can vary depending on the application, but I consistently see this as a stable metric.
00:03:56.099
Profiling is incredibly valuable; if you're not profiling, you're essentially guessing at what's slow, which is a common pitfall on certain websites. I typically use `stack Prof`, a sampling Ruby profiler, as well as `rbspy`, both of which are excellent tools.
00:04:46.259
We've utilized RailsBench, a benchmarking tool for a simple Rails app with a couple of endpoints. Running `stack Prof` against this application shows that the module triple equals takes around 1.7 percent of the runtime. Additionally, in production environments like GitHub, the `is_a?` method was taking almost four percent of the total time, which is quite significant.
00:05:54.360
Going into more detail about this profiling, I leveraged Linux Prof, a great tool for fast and efficient performance profiling, even running it in production environments. Using this tool for our load tests and on the GitHub application yielded similar results.
00:06:20.220
What I found during the profiling was that methods such as `RB_Object_kind_of?` (which check the object's class and its ancestry) are executed frequently. Understanding this gave me insight into how Ruby handles objects and methods.
00:06:45.600
Performance issues can arise when dealing with linked lists and their data structure in memory, especially considering that modern CPUs prefer tightly packed data within cache lines for better efficiency.
00:07:16.320
In particular, Ruby's way of checking class ancestry, by iterating through both class and module ancestors, can be inefficient. The memory layout affects performance—each check consumes more time due to the dispersed memory structure associated with linked lists.
00:08:33.900
Another important thing to note is that when modules are included in classes, the actual number of ancestors can surmount to a complex hierarchy. While the unique feature of Ruby allows you to include base classes into new structures, the practice can bloat class ancestry, which can lead to performance degradation.
00:09:21.660
The ideal solution would be to enhance the `is_a?` implementation rather than simplifying the class hierarchy, which might be evidence of sound engineering practices. Additionally, avoiding method calls to `is_a?` where possible and using alternatives like `instance_of?` can help mitigate this performance issue.
00:10:00.000
Ultimately, since the performance of `is_a?` tends to be slower than we anticipate, it would be beneficial to modify Ruby's internal code to optimize it—creating an assembly language version of `is_a?` would mean it could run in constant time.
00:11:45.300
Upon implementing these performance improvements within the Ruby VM, one of the approaches I took involved using a hash to store the ancestors in such a manner that we could easily check against them.
00:12:55.200
After iterating through numerous methods and determining the best approach, I ended up with an optimized constant-time implementation for the `is_a?` method based on class depth comparisons, reducing the need for iteration altogether.
00:14:15.120
The new implementation reduces the memory footprint significantly as well, making it less burdensome on resources, hence improving overall application performance.
00:15:36.900
Through sharing memory across classes based on common inheritance paths, we can ensure that we save on space and optimize performance, allowing Rails applications to run more smoothly with significant improvements.
00:17:00.120
Finally, the new implementation simplifies the use of `is_a?` across the Ruby ecosystem, allowing for faster checks without altering library or application logic significantly. I hope to inspire the community to participate in performance profiling and sharing those insights.
00:17:52.260
Thank you for your attention! I look forward to seeing how we can all work together to enhance Ruby's performance and usability.