Flyweight TypeName / Namespace cached in a shared trie#2957
Conversation
|
This also improves the performance of steep (with kaigionrails/conference-app): |
049a778 to
543012c
Compare
Cache canonical TypeName / Namespace instances behind two
positional factory methods, and route every allocation site —
Ruby helpers, the C parser, the resolver, and the environment
— through them. Repeated construction of structurally equal
values returns the same object instead of allocating fresh.
Namespace[path, absolute]
TypeName[namespace, name]
Namespace interns into a per-`absolute` trie of nested Hashes
keyed on path Symbols. TypeName interns into a two-level Hash
keyed by canonical Namespace identity and name Symbol. Both
fast paths are lock-free; cache misses take a mutex.
The C parser calls `Namespace[]` / `TypeName[]` via
`rb_funcallv`; an in-C trie walk that read intern internals
through `rb_hash_lookup` was identical in wall time on Ruby 4.0+
and was dropped to avoid coupling the parser to the cache layout.
Measured against the kaigionrails/conference-app project's RBS
collection (3,903 type names), 1 warmup + 5 runs, median:
Ruby 3.4.9 master 1.247s wall / 0.63s user
branch 1.158s wall / 0.57s user (-7% wall, -10% user)
Ruby 4.0.4 master 1.175s wall / 0.57s user
branch 1.146s wall / 0.55s user (-2.5% wall, -4% user)
Live RBS::Namespace 51,436 → 6,904; RBS::TypeName 78,493 → 22,819
after `rbs list` on Ruby 4.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
543012c to
a35ba9f
Compare
soutaro
left a comment
There was a problem hiding this comment.
Hmm. This requires a difficult architectural decision. What if we want to multi-ractor processing in RBS?
Let's merge this for now, but we may revisit this when we want to make things in RBS parallel for more performance later.
|
@soutaro Thank you for reviewing & merging.
Sorry for the lack of consideration for Ractor. AFAIK, to support Ractor, it's better to move these classes to the C extension. In this PR, I did not change the implementation of TypeName and Namespace as much as possible to keep the proposal simple. Additionally, I'm not sure it is a better way to implement the whole of them as C. If you need, I will work on another PR to reimplement them as C. What do you think? |
Cache canonical TypeName / Namespace instances behind two positional factory methods, and route every allocation site — Ruby helpers, the C parser, the resolver, and the environment — through them. Repeated construction of structurally equal values returns the same object instead of allocating fresh.
Namespace[path, absolute]
TypeName[namespace, name]
Namespace interns into a per-
absolutetrie of nested Hashes keyed on path Symbols. TypeName interns into a two-level Hash keyed by canonical Namespace identity and name Symbol. Both fast paths are lock-free; cache misses take a mutex.The C parser calls
Namespace[]/TypeName[]viarb_funcallv; an in-C trie walk that read intern internals throughrb_hash_lookupwas identical in wall time on Ruby 4.0+ and was dropped to avoid coupling the parser to the cache layout.Measured against the kaigionrails/conference-app project's RBS collection (3,903 type names), 1 warmup + 5 runs, median:
Ruby 3.4.9 master 1.247s wall / 0.63s user
branch 1.158s wall / 0.57s user (-7% wall, -10% user)
Ruby 4.0.4 master 1.175s wall / 0.57s user
branch 1.146s wall / 0.55s user (-2.5% wall, -4% user)
Live RBS::Namespace 51,436 → 6,904; RBS::TypeName 78,493 → 22,819 after
rbs liston Ruby 4.0.