ku1ik / rainbow

Ruby gem for colorizing printed text on ANSI terminals
MIT License
813 stars 68 forks source link

Optimize wrapping with SGR #123

Closed fatkodima closed 1 year ago

fatkodima commented 1 year ago

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
Warming up --------------------------------------
       wrap_with_sgr    27.455k i/100ms
optimized wrap_with_sgr
                        63.509k i/100ms
Calculating -------------------------------------
       wrap_with_sgr    324.190k (± 5.7%) i/s -      1.620M in   5.013899s
optimized wrap_with_sgr
                        677.588k (± 0.9%) i/s -      3.429M in   5.061737s

Comparison:
optimized wrap_with_sgr:   677588.1 i/s
       wrap_with_sgr:   324189.8 i/s - 2.09x  (± 0.00) slower
olleolleolle commented 1 year ago

@fatkodima Thank you for the improvement!

Let's see what we can do to release this.