When there are lots of offenses, rubocop spends ~2.5% of the time in the wrap_with_sgr method.
100% of the strings in rubocop are wrapped only once (no chaining).
This PR optimizes that method from 2x (for shorter strings) to 1.5x (for longer). For strings already having control sequences performance is the same.
Benchmark
# frozen_string_literal: true
require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
gem "benchmark-ips"
gem "rainbow", github: "sickill/rainbow"
end
module Rainbow
class StringUtils
def self.optimized_wrap_with_sgr(string, codes)
return string if codes.empty?
seq = "\e[" + codes.join(";") + "m"
if string.include?("\e")
string = string.sub(/^(\e\[([\d;]+)m)+/) { |m| m + seq }
string += "\e[0m" unless string.end_with? "\e[0m"
string
else
seq + string + "\e[0m"
end
end
end
end
codes = [1, 2]
string = "Hello world"
Benchmark.ips do |x|
x.report("wrap_with_sgr") do
Rainbow::StringUtils.wrap_with_sgr(string, codes)
end
x.report("optimized wrap_with_sgr") do
Rainbow::StringUtils.optimized_wrap_with_sgr(string, codes)
end
x.compare!
end
When there are lots of offenses, rubocop spends ~2.5% of the time in the
wrap_with_sgr
method. 100% of the strings in rubocop are wrapped only once (no chaining).This PR optimizes that method from 2x (for shorter strings) to 1.5x (for longer). For strings already having control sequences performance is the same.
Benchmark