RBS scan cache to improve overall performance - #1796
Conversation
|
I agree that the quadratic cost should be fixed. However, it also happens in the normal RDoc parse path, not just RBS. It would be simpler to implement the cache in Context itself rather than in each parser. Making Context already has a hash-based lookup: Concretely, we could add internal methods like
|
|
I will revisit this PR once #1802 will be merged. |
#1802) ## Background The Ruby parser renames an instance method `initialize` to `::new` for documentation purposes. This rename happened *after* `container.add_method`, with a comment claiming the ordering is intentional: "Rename after add_method to register duplicated 'new' and 'initialize' defined in c and ruby". The actual reason for this placement is older. In the Ripper-based streaming parser, documentation modifiers such as `:notnew:` were read *after* the method line, so at `add_method` time the parser simply did not know yet whether the method should be renamed: ```ruby # Having now read the method parameters and documentation modifiers, we # now know whether we have to rename #initialize to ::new ``` (lib/rdoc/parser/ripper_ruby.rb, removed in #1690) The Prism parser processes directives and modifier lines before `add_method`, so this constraint is gone. The post-add placement was a consequence of the streaming parser's information ordering, not a design goal — which is why it is safe to retire the post-add mutation pattern now. The comment in the current code was a port-time rationalization of an observable side effect. ## What the old placement actually did Registering the method under the `#initialize` key and renaming it afterwards had two effects: 1. `Context#methods_hash` was left keyed by a stale name (`#initialize` pointing at a method now called `::new`). This is one of the obstacles to making `Context#find_method` hash-based (see the discussion in #1796). 2. Duplicate detection in `Context#add_method` was bypassed. A class documenting both `::new` (an explicit `def self.new`, or a C-defined `new`) and `#initialize` ended up with two `::new` entries on its page. ## Change Move the rename block before `container.add_method`. All information it uses (the method name, `singleton`, and `dont_rename_initialize` set by the `:notnew:` directive) is already available at that point. With the rename in place before registration, the normal deduplication applies: the first registration wins, and the duplicate is reported by the existing "Duplicate method" warning (visible with `--verbose`). ## Corpus diff Compared per-class method lists (name, singleton, visibility, file) for whole corpora, before vs after: | Corpus | Changes | | --- | --- | | ruby/ruby | 4 classes lose a duplicated `::new` entry: `Gem::Package::TarReader`, `Gem::Package::TarWriter`, `Gem::Resolver::APISpecification`, `JSON::Ext::Generator::State` | | activesupport 8.1.3 | 1 class: `ActiveSupport::Deprecation::DeprecatedConstantProxy` | | rdoc itself | no change | All other entries are identical. Each changed class defines both `def self.new` and `def initialize` (or, for `JSON::Ext::Generator::State`, a C-defined `new` and a Ruby `initialize` — the exact "c and ruby" case the old comment referred to). On master these pages show `new` twice, with a duplicated `id="method-c-new"` anchor (invalid HTML; the index can only link to the first entry); with this change they show it once. ## Cleanup The second commit removes the `dont_rename_initialize` keyword argument of `internal_add_method`. It has never been passed a truthy value since its introduction: one call site passes an explicit `false` and the other relies on the `false` default. It is unrelated to `AnyMethod#dont_rename_initialize` (set by the `:notnew:` directive), which remains the live mechanism; removing the constant-false parameter also removes the confusion of two same-named flags in one method. --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Context#add_alias can append methods or attributes as a side effect, making mutation-side cache updates depend on Context internals. Index each collection's unconsumed tail during lookup instead, keeping synchronization in one place while preserving linear lookup.
5ab0b8b to
3c1659f
Compare
Documentation previewCommit: |
|
This change show nice results on micro-benchmarks (x5 better), but with end-to-end testing of entire documentation generation workflow improvement is around 1-2% (within run-to-run noise). It doesn't seem like @tompng I pushed my latest code changes, but I don't think it's worth it to continue. I'll be closing this PR. If you still want these changes, we can reconsider. |
RBS parsing repeatedly searched growing method and attribute arrays, making classes with many declarations quadratic. This replaces those searches with parser-local Hash indexes while leaving the ordered code-object collections unchanged.
The indexes are initialized from existing context members and updated when methods, attributes, or aliases are added. This preserves behavior for rebuilt stores, forward aliases, and legacy instance methods with a nil singleton value.
Five measured runs with 2,000 declarations per workload:
Benchmark
https://gist.github.com/skatkov/82f89eaf2269286a42a4adf966d2fd12