Finer-grained constant invalidation #5000
Closed
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Today, when you reference a static constant anywhere in your code YARV adds two instructions:
opt_getinlinecache
andopt_setinlinecache
.opt_getinlinecache
stores a cached number which refers to the global constant state (ruby_vm_global_constant_state
) that was set when the constant was last looked up. If the cached number matches the current global constant state, then it jumps to its target instruction. If it doesn't match, then it carries on to the next instruction which will look up the constant and put it on the stack.opt_setinlinecache
gets the value of the constant off the stack, looks up the current global constant state, and stores both of those values in the associatedopt_getinlinecache
instruction's cache entry.Effectively, that means any time
ruby_vm_global_constant_state
is incremented, every constant cache is busted and must be looked up again. In the case where you have a system that dynamically defines constants, this means you can pay a significant penalty on constant lookup relatively frequently. This also has implications for JITs, because it forces them to discard generated code that specialized on the constant value in the cache.The times when
ruby_vm_global_constant_state
is incremented include:You can inspect
ruby_vm_global_constant_state
by callingRubyVM.stat(:global_constant_state)
which returns the value of the current constant state.In this commit, we've added a ID table constant cache to the VM struct, which functions as a map between
ID
s and anrb_serial_t
. Effectively it's storing a map of constant name to its own cache state. This means we can changeopt_getinlinecache
andopt_setinlinecache
in the following ways.opt_getinlinecache
now stores both a constant state and a pointer to an entry in the VM's constant cache. When this instruction is executed it checks if a cache entry exists for the givenID
and that the state matches the one stored in the cache. Effectively this means that everyID
now has its own global cache state.opt_setinlinecache
now accepts an additional operand that is theID
that is being checked. It does this so that it can increment the number in the cache corresponding to the givenID
.With this model, whenever a constant changes in the ways mentioned above it only clears the cache for its specific name (as opposed to for every constant globally). This avoids a lot of invalidation. The only caveat is that when a module is included, it must invalidate all of its constants as it means that constant lookup will change for the object that is including the module.
Because
global_constant_state
is no longer a thing, the commit changesRubyVM.stat
to return a hash representing the VM's global constant cache. On start, it returns something that looks like: