From 63cec98a7c86ff262e3bf9e4728f8ff39d507192 Mon Sep 17 00:00:00 2001 From: Takumi Shotoku Date: Mon, 13 Apr 2026 21:37:06 +0900 Subject: [PATCH 1/2] Lazily initialize BasicVertex#@types_to_be_added BasicVertex#initialize was allocating an empty Hash for @types_to_be_added on every instantiation (4.3% of CPU samples). Most vertices never use this field. Initialize it lazily with ||= on first write in on_type_added. Measured with tool/dog_bench.rb (stackprof): - GC overhead: 38.1% -> 28.6% - BasicVertex#initialize: 49 samples -> 0 --- lib/typeprof/core/graph/vertex.rb | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/typeprof/core/graph/vertex.rb b/lib/typeprof/core/graph/vertex.rb index 747ccbf9e..e1e5a2997 100644 --- a/lib/typeprof/core/graph/vertex.rb +++ b/lib/typeprof/core/graph/vertex.rb @@ -2,7 +2,6 @@ module TypeProf::Core class BasicVertex def initialize(types) @types = types - @types_to_be_added = {} end attr_reader :types @@ -10,13 +9,15 @@ def initialize(types) def each_type(&blk) @types.each_key(&blk) - until @types_to_be_added.empty? - h = @types_to_be_added.dup - h.each do |ty, source| - @types[ty] = source + if @types_to_be_added + until @types_to_be_added.empty? + h = @types_to_be_added.dup + h.each do |ty, source| + @types[ty] = source + end + @types_to_be_added.clear + h.each_key(&blk) end - @types_to_be_added.clear - h.each_key(&blk) end end @@ -151,7 +152,7 @@ def on_type_added(genv, src_var, added_types) begin @types[ty] = set rescue - @types_to_be_added[ty] = set + (@types_to_be_added ||= {})[ty] = set end set << src_var (new_added_types ||= []) << ty @@ -167,7 +168,7 @@ def on_type_added(genv, src_var, added_types) def on_type_removed(genv, src_var, removed_types) new_removed_types = nil removed_types.each do |ty| - raise "!!! not implemented" if @types_to_be_added[ty] + raise "!!! not implemented" if @types_to_be_added&.[](ty) @types[ty].delete(src_var) || raise if @types[ty].empty? @types.delete(ty) || raise From 88d8b356d958f5c3f8c5559fe05d7c7aaaf04c21 Mon Sep 17 00:00:00 2001 From: Takumi Shotoku Date: Mon, 13 Apr 2026 21:37:12 +0900 Subject: [PATCH 2/2] Lazily initialize MethodEntity collections MethodEntity#initialize was allocating 4 Sets and 1 Hash eagerly. Use lazy accessor methods to defer allocation until first use. Most MethodEntity instances are created during module resolution but many fields are never populated. Measured with tool/dog_bench.rb (stackprof, cumulative with prior commits): - GC overhead: 28.6% -> 23.7% - Total samples: 1038 -> 1001 --- lib/typeprof/core/env/method_entity.rb | 33 ++++++++++++++------------ 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/lib/typeprof/core/env/method_entity.rb b/lib/typeprof/core/env/method_entity.rb index b5bfa98c1..24734f03b 100644 --- a/lib/typeprof/core/env/method_entity.rb +++ b/lib/typeprof/core/env/method_entity.rb @@ -2,60 +2,63 @@ module TypeProf::Core class MethodEntity def initialize @builtin = nil - @decls = Set.empty - @overloading_decls = Set.empty - @defs = Set.empty - @aliases = {} - @method_call_boxes = Set.empty end - attr_reader :decls, :defs, :aliases, :method_call_boxes attr_accessor :builtin + def decls = @decls ||= Set.empty + def defs = @defs ||= Set.empty + def aliases = @aliases ||= {} + def method_call_boxes = @method_call_boxes ||= Set.empty + + private def overloading_decls = @overloading_decls ||= Set.empty + def add_decl(decl) if decl.overloading - @overloading_decls << decl + overloading_decls << decl else - @decls << decl + decls << decl end end def remove_decl(decl) if decl.overloading - @overloading_decls.delete(decl) || raise + overloading_decls.delete(decl) || raise else - @decls.delete(decl) || raise + decls.delete(decl) || raise end end def add_def(mdef) - @defs << mdef + defs << mdef self end def remove_def(mdef) - @defs.delete(mdef) || raise + defs.delete(mdef) || raise end def add_alias(node, old_mid) - @aliases[node] = old_mid + aliases[node] = old_mid end def remove_alias(node) - @aliases.delete(node) || raise + aliases.delete(node) || raise end def exist? - @builtin || !@decls.empty? || !@defs.empty? + @builtin || (@decls && !@decls.empty?) || (@defs && !@defs.empty?) end def add_run_all_mdefs(genv) + return unless @defs @defs.each do |mdef| genv.add_run(mdef) end end def add_run_all_method_call_boxes(genv) + return unless @method_call_boxes @method_call_boxes.each do |box| genv.add_run(box) end