In our opinion,
this PR provides one of the simplest yet most performant speed improvements to Rails 7.
Ruby recently
introduced an inline cache
for class variable reads.
This allows for values to be read from the cache instead of traversing a complex inheritance tree to resolve a class variable value.
When class variables are read,
Ruby needs to check each class in the inheritance tree to ensure that the class variable isn’t set on any other classes in the tree.
As you can imagine this becomes an O(n) problem.
As the number of nodes increases in a tree,
the read performance degrades linearly.
Let’s look at a demonstration using a class that inherits 1 module,
30 modules and finally 100 modules.
This is how Ruby performs without cache:
Now, let’s look at the performance with the cache.
On Ruby master,
including 100 modules is 8.5x slower than including 1 module.
However,
with the cache,
there is no performance difference between including 1 module and including 100 modules!
Now let’s look at how the Rails core team bought this performance improvement to Rails.
Before
ActiveRecord::Base.logger is a cvar that has 63 modules in the inheritance tree.
We can check it out ourselves by calling:
Opening up ActiveRecord core,
this is how logger is defined.
Since it is defined as a mattr_accessor and not a class variable,
it is unable to utilize the performance improvements introduced by the latest Ruby.
Now that’s a huge spike in improvement – nearly 7x! This is a great example of a real-world improvement to Rails applications.
Gotchas
There a just a few gotchas with this change.
Since ActiveRecord::Base.logger is now a class_attribute,
we can no longer access it directly through @@logger,
nor can setting logger = on a subclass change the parent’s logger anymore.
However,
this is a very small inconvenience as most use logger as just a method –
but we can never be careful enough!
Now,
time to comb through your codebases and see how you can utilize this performance improvement!