summaryrefslogtreecommitdiff
path: root/spec/ruby/core/dir/scan_spec.rb
AgeCommit message (Collapse)Author
2026-05-08Update to ruby/spec@680fc69Benoit Daloze
2026-02-12Dir.scan: return or yield children along with their typeJean Boussier
[Feature #21800] There are numerous ruby tools that need to recursively scan the project directory, such as Zeitwerk, rubocop, etc. All of them end up listing childs of a directory then for each child emit a `stat` call to check if it's a directory or not. This is common enough for a pattern that on most operating systems, `struct dirent` include a `dtype` member that allows to check the file type without issuing a any extra system calls. By yielding that type, we can make these routines twice as fast. ``` $ hyperfine './miniruby --disable-all --yjit ../test.rb' 'OPT=1 ./miniruby --disable-all --yjit ../test.rb' Benchmark 1: ./miniruby --disable-all --yjit ../test.rb Time (mean ± σ): 1.428 s ± 0.062 s [User: 0.342 s, System: 1.070 s] Range (min … max): 1.396 s … 1.601 s 10 runs Benchmark 2: OPT=1 ./miniruby --disable-all --yjit ../test.rb Time (mean ± σ): 673.8 ms ± 5.8 ms [User: 146.0 ms, System: 527.3 ms] Range (min … max): 659.7 ms … 679.6 ms 10 runs Summary OPT=1 ./miniruby --disable-all --yjit ../test.rb ran 2.12 ± 0.09 times faster than ./miniruby --disable-all --yjit ../test.rb ``` ```ruby if ENV['OPT'] def count_ruby_files count = 0 queue = [File.expand_path(__dir__)] while dir = queue.pop Dir.scan(dir) do |name, type| next if name.start_with?(".") case type when :directory queue << File.join(dir, name) when :file count += 1 if name.end_with?(".rb") end end end count end else def count_ruby_files count = 0 queue = [File.expand_path(__dir__)] while dir = queue.pop Dir.each_child(dir) do |name| next if name.start_with?(".") abspath = File.join(dir, name) if File.directory?(abspath) queue << abspath else count += 1 if name.end_with?(".rb") end end end count end end 10.times do count_ruby_files end ```