From ed1aadbcbc8bdfefcc8ff1eecf35b95183fa77ab Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Wed, 18 Sep 2024 10:59:38 +0200 Subject: [PATCH] Optimize `Base.callbacks_needed` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This matters since there are quite a few public methods per cop class. In total, it gets called ~57k times, or ~104 times per cop class. `method_defined?` is faster than string checking, so move that first. It also eliminates most of the checks, Base contains 98 methods. Current: ``` $ hyperfine -w 10 -r 25 "bundle exec rubocop Rakefile --cache=false" Benchmark 1: bundle exec rubocop Rakefile --cache=false Time (mean ± σ): 932.8 ms ± 7.6 ms [User: 828.0 ms, System: 104.0 ms] Range (min … max): 919.5 ms … 952.2 ms 25 runs ``` With String: ``` $ hyperfine -w 10 -r 25 "bundle exec rubocop Rakefile --cache=false" Benchmark 1: bundle exec rubocop Rakefile --cache=false Time (mean ± σ): 924.1 ms ± 7.8 ms [User: 816.3 ms, System: 107.3 ms] Range (min … max): 912.8 ms … 941.5 ms 25 runs ``` With String + order switched: ``` $ hyperfine -w 10 -r 25 "bundle exec rubocop Rakefile --cache=false" Benchmark 1: bundle exec rubocop Rakefile --cache=false Time (mean ± σ): 920.7 ms ± 6.9 ms [User: 814.5 ms, System: 105.6 ms] Range (min … max): 906.9 ms … 932.5 ms 25 runs ``` ~ 1.3% faster --- lib/rubocop/cop/base.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/rubocop/cop/base.rb b/lib/rubocop/cop/base.rb index 1d59da779221..ad0a77b13d5d 100644 --- a/lib/rubocop/cop/base.rb +++ b/lib/rubocop/cop/base.rb @@ -322,8 +322,12 @@ def callbacks_needed # @api private def self.callbacks_needed @callbacks_needed ||= public_instance_methods.select do |m| - m.start_with?(/on_|after_/) && - !Base.method_defined?(m) # exclude standard "callbacks" like 'on_begin_investigation' + # OPTIMIZE: Check method existence first to make fewer `start_with?` calls. + # At the time of writing this comment, this excludes 98 of ~104 methods. + # `start_with?` with two string arguments instead of a regex is faster + # in this specific case as well. + !Base.method_defined?(m) && # exclude standard "callbacks" like 'on_begin_investigation' + m.start_with?('on_', 'after_') end end # rubocop:enable Layout/ClassStructure