Skip to content

Share method entries between shapes via RBS Method identity - #2248

Open
soutaro wants to merge 1 commit into
claude/steep-type-checking-perf-2owf10from
claude/steep-shared-method-entries
Open

Share method entries between shapes via RBS Method identity#2248
soutaro wants to merge 1 commit into
claude/steep-type-checking-perf-2owf10from
claude/steep-shared-method-entries

Conversation

@soutaro

@soutaro soutaro commented Jul 9, 2026

Copy link
Copy Markdown
Owner

Stacked on #2247. (Retarget to master after #2247 merges.)

Problem

RBS::DefinitionBuilder returns the identical Definition::Method object for methods inherited without any change (no overriding, no type application) — Object#tap in build_instance(::String) is the same object as in build_instance(::Object). Steep, however, rebuilt Shape::Entry/MethodOverload wrappers for every method of every type.

Measured on Steep's own environment (765 classes): the definitions contain 118,475 (type, method) entries, of which only 21,399 (18%) are distinct Method objects — 82% of shape construction work was duplicated, mostly for methods inherited from Object/Kernel/Enumerable.

Change

Interface::Builder#method_entry caches entries in an identity-keyed Hash (compare_by_identity), so an entry is built once and shared by the shapes of every type that inherits the same Method object.

The only type-dependent entry is the class method (defined in Kernel), whose return type is replaced with the singleton type of the owner (replace_kernel_class), so it bypasses the cache. The other rewrites (method_name_for, replace_primitive_method, add_implicitly_returns_nil) depend only on the type definition itself and are safe to share.

Results

Checking Steep's app target (single process, interleaved A/B on the same machine):

before after
wall 32.9s 30.1s (−8.5%)
GC time 6.9s 5.6s (−19%)
allocated objects 30.4M 27.0M (−11%)
peak RSS 408MB 348MB (−15%)

Eagerly building the shapes of all 1,646 classes in the environment retains 161MB instead of 283MB (−43%), which also helps a future warmup-then-fork worker design.

Verification

  • test/type_check_test.rb (133 runs), test/subtyping_test.rb (50 runs), test/interface_test.rb all pass
  • steep check self-type-check reports the same 23 problems as the base branch (unchanged)
  • test/interface_builder_test.rb has 1 pre-existing failure on the base branch as well (rbs master changed Hash#[] to take _Key), unrelated to this change

🤖 Generated with Claude Code

https://claude.ai/code/session_011WWMEZWwe2481yi4BZNKLo


Generated by Claude Code

RBS::DefinitionBuilder returns the identical Definition::Method object
for methods inherited without any change (no overriding, no type
application). Steep was rebuilding Shape::Entry/MethodOverload wrappers
for every one of them per type: across the 765 classes of Steep's own
environment, the definitions contain 118,475 (type, method) entries of
which only 21,399 (18%) are distinct Method objects -- 82% of shape
construction work was duplicated.

Interface::Builder#method_entry now caches entries in an identity-keyed
Hash, so a method entry is built once and shared by the shapes of every
type that inherits it. The only type-dependent entry is the (Kernel)
'class' method, whose return type is replaced with the singleton type
of the owner, so it bypasses the cache.

Checking Steep's app target (single process, interleaved A/B):
wall 32.9s -> 30.1s, GC 6.9s -> 5.6s, allocations 30.4M -> 27.0M,
peak RSS 408MB -> 348MB. Building the shapes of all 1,646 classes in
the environment eagerly retains 161MB instead of 283MB, which also
helps a future warmup-then-fork worker design.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011WWMEZWwe2481yi4BZNKLo
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants