Improve the performance of the `ApplicationRecord#underscore` method
What does this MR do and why?
Using SafeRequestStore is safe(not thread safe but using the thread current anyway so each thread will have its own value) but we shouldn't be worried about the thread safety of this method as all the calls to this method would return the exact same value, therefore, memoizing the value by using a class instance variable should be safe, and more performant(~9 times).
Benchmark
Benchmark script
GL_PATH = "YOUR_PATH_TO_GITLAB"
ENV['ENABLE_BOOTSNAP'] = '0'
ENV['BUNDLE_GEMFILE'] = "#{GL_PATH}/Gemfile"
system "BUNDLE_GEMFILE=#{ENV['BUNDLE_GEMFILE']} bundle install"
puts "Loading the Application..."
require "#{GL_PATH}/config/environment.rb"
Rails.configuration.eager_load_namespaces.each(&:eager_load!)
puts "Application is loaded."
require 'benchmark/ips'
RequestStore.begin!
Benchmark.ips do |b|
b.report('existing underscore') do
Ci::Pipeline.underscore
end
b.report('to_s underscore') do
Ci::Pipeline.to_s.underscore
end
b.report('new underscore') do
Ci::Pipeline.new_underscore
end
b.report('mutex underscore') do
Ci::Pipeline.mutex_underscore
end
b.compare!
end
Patch for other methods
diff --git a/app/models/application_record.rb b/app/models/application_record.rb
index 291375f647c4..360e791a64f6 100644
--- a/app/models/application_record.rb
+++ b/app/models/application_record.rb
@@ -98,6 +98,18 @@ def self.underscore
Gitlab::SafeRequestStore.fetch("model:#{self}:underscore") { to_s.underscore }
end
+ def self.new_underscore
+ @new_underscore ||= to_s.underscore
+ end
+
+ MUTEX = Mutex.new
+
+ def self.mutex_underscore # this would probably perform worse under load with many threads though
+ MUTEX.synchronize do
+ @new_underscore ||= to_s.underscore
+ end
+ end
+
def self.where_exists(query)
where('EXISTS (?)', query.select(1))
end
Warming up --------------------------------------
existing underscore 83.144k i/100ms
to_s underscore 52.636k i/100ms
new underscore 1.465M i/100ms
mutex underscore 779.233k i/100ms
Calculating -------------------------------------
existing underscore 1.673M (± 4.9%) i/s - 8.398M in 5.030956s
to_s underscore 533.463k (± 4.1%) i/s - 2.684M in 5.040301s
new underscore 14.555M (± 2.6%) i/s - 73.261M in 5.037141s
mutex underscore 7.774M (± 0.4%) i/s - 38.962M in 5.012015s
Comparison:
new underscore: 14555381.7 i/s
mutex underscore: 7773760.4 i/s - 1.87x slower
existing underscore: 1673418.3 i/s - 8.70x slower
to_s underscore: 533463.3 i/s - 27.28x slower
MR acceptance checklist
This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.
-
I have evaluated the MR acceptance checklist for this MR.
Edited by Mehmet Emin INAC