From 0472eb5cf2843d8acb572b79500190fef6637607 Mon Sep 17 00:00:00 2001 From: st0012 Date: Sun, 15 Mar 2026 16:13:32 +0000 Subject: [PATCH 1/3] Memoize search_snippet on code objects for 13x faster warm search index search_snippet calls snippet() which does full markup parsing + HTML conversion. For ruby/ruby with thousands of methods, this dominated the search index build time (5.2s cold). Cache the computed snippet on each code object. After a file change, only the affected objects recompute (methods are recreated; ClassModule snippets are invalidated in rebuild_comment_from_location). Results: search_index_warm drops from ~5200ms to ~390ms. --- lib/rdoc/code_object/class_module.rb | 5 ++++- lib/rdoc/code_object/constant.rb | 2 +- lib/rdoc/code_object/method_attr.rb | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/rdoc/code_object/class_module.rb b/lib/rdoc/code_object/class_module.rb index b564059ae7..370f95914b 100644 --- a/lib/rdoc/code_object/class_module.rb +++ b/lib/rdoc/code_object/class_module.rb @@ -741,10 +741,12 @@ def search_record # Returns an HTML snippet of the first comment for search results. def search_snippet + return @search_snippet_cache if instance_variable_defined?(:@search_snippet_cache) + first_comment = @comment_location.each_value.first&.first return '' unless first_comment && !first_comment.empty? - snippet(first_comment) + @search_snippet_cache = snippet(first_comment) end ## @@ -757,6 +759,7 @@ def rebuild_comment_from_location } merged = texts.join("\n---\n") @comment = merged.empty? ? '' : RDoc::Comment.new(merged) + remove_instance_variable(:@search_snippet_cache) if instance_variable_defined?(:@search_snippet_cache) end ## diff --git a/lib/rdoc/code_object/constant.rb b/lib/rdoc/code_object/constant.rb index 20015b86f1..d49622548d 100644 --- a/lib/rdoc/code_object/constant.rb +++ b/lib/rdoc/code_object/constant.rb @@ -168,7 +168,7 @@ def path def search_snippet return '' if comment.empty? - snippet(comment) + @search_snippet_cache ||= snippet(comment) end def pretty_print(q) # :nodoc: diff --git a/lib/rdoc/code_object/method_attr.rb b/lib/rdoc/code_object/method_attr.rb index 3169640982..745c040e3c 100644 --- a/lib/rdoc/code_object/method_attr.rb +++ b/lib/rdoc/code_object/method_attr.rb @@ -397,7 +397,7 @@ def search_record def search_snippet return '' if @comment.empty? - snippet(@comment) + @search_snippet_cache ||= snippet(@comment) end def to_s # :nodoc: From 105afbfe18e3b88292f6d3a05257f65fbc39205c Mon Sep 17 00:00:00 2001 From: st0012 Date: Sun, 15 Mar 2026 16:31:37 +0000 Subject: [PATCH 2/3] Pre-build search index on server startup Build the search index before accepting connections so it's ready for the first request. This moves the 3.6s cold build cost from the first search_data.js request to the startup phase where the user is already waiting for parsing to complete. Also populates the search_snippet caches on all code objects, making subsequent rebuilds after file changes much faster (~380ms). --- lib/rdoc/server.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/rdoc/server.rb b/lib/rdoc/server.rb index 9dbb342908..70676680d4 100644 --- a/lib/rdoc/server.rb +++ b/lib/rdoc/server.rb @@ -85,6 +85,7 @@ def start @running = true @watcher_thread = start_watcher(@rdoc.last_modified.keys) + prebuild_search_index url = "http://localhost:#{@port}" $stderr.puts "\nServing documentation at: \e]8;;#{url}\e\\#{url}\e]8;;\e\\" @@ -118,6 +119,20 @@ def create_generator gen end + ## + # Pre-builds the search index so it's ready when the user first + # requests search_data.js. This avoids a multi-second delay + # on the first search request. Called before accepting connections + # so no synchronization is needed. + + def prebuild_search_index + index = @generator.build_search_index + @page_cache['js/search_data.js'] = "var search_data = #{JSON.generate(index: index)};" + $stderr.puts "Search index ready (#{index.size} entries)" + rescue => e + $stderr.puts "Search index pre-build error: #{e.message}" + end + ## # Reads an HTTP request from +client+ and dispatches to the router. From d7f367d406639af9a9cc982b59e97cced393c969 Mon Sep 17 00:00:00 2001 From: st0012 Date: Sun, 15 Mar 2026 16:34:42 +0000 Subject: [PATCH 3/3] Make methods list lazy to speed up refresh_store_data The methods list (flat_map + sort of 10K+ methods from all classes) was computed eagerly on every refresh_store_data call. Since it's only needed for building the search index, compute it lazily on first access and cache until the next refresh. Results: refresh_store_time drops from 64ms to 2.9ms (22x faster). reparse_cycle_time drops from 309ms to 222ms. --- lib/rdoc/generator/aliki.rb | 2 +- lib/rdoc/generator/darkfish.rb | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/rdoc/generator/aliki.rb b/lib/rdoc/generator/aliki.rb index d8314196f9..45fe14a66f 100644 --- a/lib/rdoc/generator/aliki.rb +++ b/lib/rdoc/generator/aliki.rb @@ -86,7 +86,7 @@ def build_search_index end end - @methods.each do |method| + methods.each do |method| next unless method.display? index << build_method_entry(method) diff --git a/lib/rdoc/generator/darkfish.rb b/lib/rdoc/generator/darkfish.rb index 8e63f5bd23..fd12c1dc74 100644 --- a/lib/rdoc/generator/darkfish.rb +++ b/lib/rdoc/generator/darkfish.rb @@ -118,9 +118,13 @@ class RDoc::Generator::Darkfish attr_reader :json_index ## - # Methods to be displayed by this generator + # Methods to be displayed by this generator. + # Computed lazily from @classes to avoid sorting 10K+ methods on every + # refresh_store_data call. - attr_reader :methods + def methods + @methods ||= @classes&.flat_map { |m| m.method_list }&.sort + end ## # Sorted list of classes and modules to be displayed by this generator @@ -588,10 +592,11 @@ def setup def refresh_store_data @classes = @store.all_classes_and_modules.sort @files = @store.all_files.sort - @methods = @classes.flat_map { |m| m.method_list }.sort + @methods = nil # computed lazily by #all_methods @modsort = get_sorted_module_list @classes end + ## # Creates a template from its components and the +body_file+. #