Reuse decls in resolve_type_names when no type names change#2977
Open
tk0miya wants to merge 1 commit into
Open
Reuse decls in resolve_type_names when no type names change#2977tk0miya wants to merge 1 commit into
tk0miya wants to merge 1 commit into
Conversation
tk0miya
commented
May 26, 2026
| # changed elements substituted in. Callers detect a no-op by comparing | ||
| # the return value with the input via `equal?`, which avoids | ||
| # allocating a `[mapped, changed]` tuple on every invocation. | ||
| def map_if_changed(array, &) |
Contributor
Author
There was a problem hiding this comment.
I'm not sure where the best location for these utility methods is.
Contributor
Author
|
This is a benchmark result for this change. Please let me know if you need the benchmark scripts. I'll commit them too. master:proposal: |
`resolve_type_names` previously rebuilt every declaration, member, and
type even when type name resolution did not change anything. The first
resolve must produce absolutized type names, so its cost is
unavoidable; but the second and later resolves were re-allocating
identical structures for no benefit, pressuring GC heavily.
This change makes every `map_type_name` / `map_type` / `resolve_*`
helper return its receiver when each child maps back to a value
`equal?` to the original. Combined with the existing flyweight
behavior of `TypeName` and `Namespace`, declarations whose type names
were already absolute are now reused verbatim across resolves.
The first resolve is therefore unchanged in both wall time and
allocations. The numbers below compare the second-and-later resolves
only, measured on conference-app (kaigionrails/conference-app):
- allocated per resolve: 20.60 MB / 387,664 objects
-> 3.52 MB / 64,293 objects (-83%)
- retained over 10 resolves: 18.36 MB / 346,343 objects
-> 1.25 MB / 22,973 objects (-93%)
- resolve wall time, p99: 112.7 ms -> 98.3 ms (-12.8%)
- GC major / 50 resolves: 4 -> 1 (-75%)
Single-shot CLI usage (`rbs list` etc.) calls resolve_type_names only
once, so it sees no change. Long-running clients such as Steep that
re-resolve repeatedly are the primary beneficiaries.
56c6d46 to
79ac920
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
resolve_type_namespreviously rebuilt every declaration, member, and type even when type name resolution did not change anything. The first resolve must produce absolutized type names, so its cost is unavoidable; but the second and later resolves were re-allocating identical structures for no benefit, pressuring GC heavily.This change makes every
map_type_name/map_type/resolve_*helper return its receiver when each child maps back to a valueequal?to the original. Combined with the existing flyweight behavior ofTypeNameandNamespace, declarations whose type names were already absolute are now reused verbatim across resolves.The first resolve is therefore unchanged in both wall time and allocations. The numbers below compare the second-and-later resolves only, measured on conference-app (kaigionrails/conference-app):
-> 3.52 MB / 64,293 objects (-83%)
-> 1.25 MB / 22,973 objects (-93%)
Single-shot CLI usage (
rbs listetc.) calls resolve_type_names only once, so it sees no change. Long-running clients such as Steep that re-resolve repeatedly are the primary beneficiaries.