Skip to content

Commit 67e4961

Browse files
authored
Lazy page index in RDoc::Store (42.7% perf improvement) (#1811)
`RDoc::Store#page` previously scanned every known file for each page lookup, including repeated misses. This adds a lazily built hash index keyed by both `page_name` and `base_name`, preserving existing first-match behavior when names collide. The index is invalidated whenever files are added, removed, or bulk-loaded. The implementation remains private to `RDoc::Store` and adds no public API or configuration. ## Benchmark For testing I used latest rdoc(master) + rdoc-markdown gem. Generation was done on a rails codebase. | Metric | Before | After | Change | | --- | ---: | ---: | ---: | | Generation time | 74.91s | 42.08s | -43.8% | | Total time | 88.52s | 50.76s | -42.7% | | Peak RSS | 411.1 MiB | 422.8 MiB | +2.8% | Total time to generate documentation decreased from 88.52s to 50.76s (42.7%).
1 parent 976f13a commit 67e4961

3 files changed

Lines changed: 60 additions & 2 deletions

File tree

‎lib/rdoc/store.rb‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ def add_file(absolute_name, relative_name: absolute_name, parser: nil)
182182
top_level.store = self
183183
@files_hash[relative_name] = top_level
184184
@text_files_hash[relative_name] = top_level if top_level.text?
185+
@page_index = nil
185186
end
186187

187188
top_level
@@ -204,6 +205,7 @@ def remove_file(relative_name)
204205
@c_singleton_class_variables.delete(relative_name)
205206
return unless top_level
206207

208+
@page_index = nil
207209
remove_classes_and_modules(top_level.classes_or_modules)
208210
end
209211

@@ -743,6 +745,7 @@ def load_all
743745
end
744746
end
745747

748+
@page_index = nil
746749
@cache[:pages].each do |page_name|
747750
page = load_page page_name
748751
@files_hash[page_name] = page
@@ -912,9 +915,12 @@ def modules_hash
912915
# Returns the RDoc::TopLevel that is a file and has the given +name+
913916

914917
def page(name)
915-
@files_hash.each_value.find do |file|
916-
file.page_name == name or file.base_name == name
918+
@page_index ||= @files_hash.each_value.each_with_object({}) do |file, index|
919+
index[file.page_name] ||= file
920+
index[file.base_name] ||= file
917921
end
922+
923+
@page_index[name]
918924
end
919925

920926
##

‎test/rdoc/rdoc_rubygems_hook_test.rb‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,19 @@ def test_generate_all
146146
assert_equal 'MyTitle', rdoc.store.main
147147
end
148148

149+
def test_generate_rdoc_with_page_cross_reference
150+
@hook.generate_rdoc = true
151+
@hook.generate_ri = false
152+
@a.extra_rdoc_files << 'GUIDE.md'
153+
154+
File.write File.join(@a.gem_dir, 'GUIDE.md'), '# Guide'
155+
File.write File.join(@a.gem_dir, 'lib', 'a.rb'), "# See rdoc-ref:GUIDE\nclass A; end\n"
156+
157+
@hook.generate
158+
159+
assert @hook.rdoc_installed?
160+
end
161+
149162
def test_generate_configuration_rdoc_array
150163
Gem.configuration[:rdoc] = %w[-A]
151164

‎test/rdoc/rdoc_store_test.rb‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,45 @@ def test_page_with_extension
650650
assert_equal page, @store.page('PAGE.txt')
651651
end
652652

653+
def test_page_index_linear_performance
654+
assert_linear_performance((1..4).map { |i| 10**i }) do |count|
655+
store = RDoc::Store.new RDoc::Options.new
656+
count.times { |i| store.add_file "page_#{i}.txt" }
657+
658+
count.times do
659+
store.page 'page_0'
660+
store.page 'missing'
661+
end
662+
end
663+
end
664+
665+
def test_page_index_invalidated_when_files_change
666+
assert_nil @store.page('PAGE')
667+
668+
first_page = @store.add_file 'PAGE.txt', parser: RDoc::Parser::Simple
669+
assert_same first_page, @store.page('PAGE')
670+
671+
second_page = @store.add_file 'doc/PAGE.md', parser: RDoc::Parser::Simple
672+
assert_same first_page, @store.page('PAGE')
673+
674+
@store.remove_file 'PAGE.txt'
675+
assert_same second_page, @store.page('PAGE')
676+
end
677+
678+
def test_page_index_invalidated_by_load_all
679+
FileUtils.mkdir_p @tmpdir
680+
source = RDoc::Store.new RDoc::Options.new, path: @tmpdir
681+
source.add_file 'README.txt', parser: RDoc::Parser::Simple
682+
source.save
683+
684+
loaded = RDoc::Store.new RDoc::Options.new, path: @tmpdir
685+
assert_nil loaded.page('README')
686+
687+
loaded.load_all
688+
689+
assert_equal 'README.txt', loaded.page('README').relative_name
690+
end
691+
653692
def test_save
654693
FileUtils.mkdir_p @tmpdir
655694

0 commit comments

Comments
 (0)