Skip to content

Instantly share code, notes, and snippets.

@shioyama
Last active October 15, 2020 06:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save shioyama/07d0d92fd3ab58385010b12af4ac734c to your computer and use it in GitHub Desktop.
Save shioyama/07d0d92fd3ab58385010b12af4ac734c to your computer and use it in GitHub Desktop.
Remove pry, don't need it
#!/usr/bin/env ruby
# frozen_string_literal: true
#
# This script demonstrates a performance regression introduced in https://github.com/rails/rails/pull/34405
# That PR patched ActiveSupport::SafeBuffer methods gsub, gsub!, sub and sub! to set backreferences, but
# in doing so incurred a significant penalty.
#
# This script creates a random string with texts at random locations which are swapped with gsub. The string
# is random but seeded the same each run, so the same length produces the same string.
#
# Run without the -a option to test performance with Ruby's String class.
# Run with -a and an activesupport version to test performance with any version of ActiveSupport
#
# e.g.
#
# ruby gsub_benchmark.rb -i 10000
# ruby gsub_benchmark.rb -i 10000 -a 6.0.3.2
# ruby gsub_benchmark.rb -i 10000 -a 5.2
require 'optparse'
NUM_ITERATIONS = 10_000
LENGTH=100_000
NUM_MATCHES = 100
options = {
iterations: NUM_ITERATIONS,
length: LENGTH,
matches: NUM_MATCHES,
active_support_version: nil
}
parser = OptionParser.new do |opts|
opts.banner = "USAGE: measure_swaptime [options]"
opts.on("-i", "--iterations [NUM]", Integer, "Number of iterations (Default: #{NUM_ITERATIONS})") do |v|
options[:iterations] = v
end
opts.on("-l", "--length [LENGTH]", Integer, "Length of string (Default: #{LENGTH})") do |v|
options[:length] = v
end
opts.on("-m", "--matches [NUM]", Integer, "Number of matches in string (Default: #{NUM_MATCHES})") do |v|
options[:matches] = v
end
opts.on("-a", "--active-support [VERSION]", String, "Set ActiveSupport version (Default: ActiveSupport is *not* required)") do |v|
options[:active_support_version] = v
end
end
parser.parse!
require 'bundler/inline'
gemfile do
source "https://rubygems.org"
gem 'activesupport', options[:active_support_version], require: 'active_support' if options[:active_support_version]
end
str_class = options[:active_support_version] ? ActiveSupport::SafeBuffer : String
str = str_class.new
puts "String Class: #{str_class.name}"
if options[:active_support_version]
puts "ActiveSupport version: #{options[:active_support_version]}."
else
puts "ActiveSupport is not loaded."
end
srand(0) # set seed to ensure we always get the same string
while str.length < options[:length]
str << rand(36**1024).to_s(36) + "\n"
end
matcher = /foo/
source = 'foo'
target = 'bar'
options[:matches].times do
str.insert(rand(str.length), source)
end
require 'benchmark'
GC.start
durations = []
options[:iterations].times do |i|
duped = str.dup
# ensure GC plays no role by force-triggering it every 100 iterations
GC.start if i % 100 == 0
result = Benchmark.measure do
duped = duped.gsub(matcher) do
target
end
raise(Exception, "No swaps performed") if !(duped =~ /#{target}/)
end
durations << result.utime
end
puts "Max: #{durations.max*1000} ms"
puts "Min: #{durations.min*1000} ms"
puts "Average: #{durations.sum / durations.size * 1000} ms"
puts "Median: #{durations.sort[durations.length / 2] * 1000} ms"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment