Skip to content

Conversation

@casperisfine
Copy link
Contributor

Followup: #41296, #41303

There a mixed bag of small optimizations here:

  • Since underscores are converted to spaces and that we remove all leading underscores. By changing the order of operations we can leverage the faster .lstrip!.
  • Corrected the gsub regexp to no longer match empty strings (+ vs *).
  • Modify the matched strings in place to save on allocations
require 'benchmark/ips'
require 'active_support/all'

module ActiveSupport
  module Inflector
    def humanize2(lower_case_and_underscored_word, capitalize: true, keep_id_suffix: false)
      result = lower_case_and_underscored_word.to_s.dup

      inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) }

      result.tr!("_", " ")
      result.lstrip!
      unless keep_id_suffix
        result.delete_suffix!(" id")
      end

      result.gsub!(/([a-z\d]+)/i) do |match|
        match.downcase!
        inflections.acronyms[match] || match
      end

      if capitalize
        result.sub!(/\A\w/) do |match|
          match.upcase!
          match
        end
      end

      result
    end
  end
end

%w(foo foo_bar_id ____foo_bar).each do |str|
  puts "== Comparing with #{str.inspect} (#{RUBY_VERSION}) =="
  unless ActiveSupport::Inflector.humanize(str) == ActiveSupport::Inflector.humanize2(str)
    raise "#{ActiveSupport::Inflector.humanize2(str)} != #{ActiveSupport::Inflector.humanize(str)}"
  end

  Benchmark.ips do |x|
    x.report('humanize') { ActiveSupport::Inflector.humanize(str) }
    x.report('humanize2') { ActiveSupport::Inflector.humanize2(str) }
    x.compare!
  end
  puts
end
== Comparing with "foo" (2.7.2) ==
Warming up --------------------------------------
            humanize    25.593k i/100ms
           humanize2    29.256k i/100ms
Calculating -------------------------------------
            humanize    263.989k (± 1.9%) i/s -      1.331M in   5.043110s
           humanize2    299.883k (± 2.2%) i/s -      1.521M in   5.075478s

Comparison:
           humanize2:   299882.5 i/s
            humanize:   263989.1 i/s - 1.14x  (± 0.00) slower

== Comparing with "foo_bar_id" (2.7.2) ==
Warming up --------------------------------------
            humanize    18.187k i/100ms
           humanize2    25.678k i/100ms
Calculating -------------------------------------
            humanize    183.702k (± 1.5%) i/s -    927.537k in   5.050326s
           humanize2    250.470k (± 2.5%) i/s -      1.258M in   5.026682s

Comparison:
           humanize2:   250469.6 i/s
            humanize:   183702.3 i/s - 1.36x  (± 0.00) slower

== Comparing with "____foo_bar" (2.7.2) ==
Warming up --------------------------------------
            humanize    18.577k i/100ms
           humanize2    24.686k i/100ms
Calculating -------------------------------------
            humanize    188.868k (± 1.5%) i/s -    947.427k in   5.017524s
           humanize2    255.650k (± 1.8%) i/s -      1.284M in   5.022833s

Comparison:
           humanize2:   255649.8 i/s

@kaspth @rafaelfranca @etiennebarrie

There a mixed bag of small optimizations here:

  - Since underscores are converted to spaces and that we remove all leading underscores. By changing the order of operations we can leverage the faster `.lstrip!`.
  - Corrected the gsub regexp to no longer match empty strings (`+` vs `*`).
  - Modify the matched strings in place to save on allocations

```ruby
require 'benchmark/ips'
require 'active_support/all'

module ActiveSupport
  module Inflector
    def humanize2(lower_case_and_underscored_word, capitalize: true, keep_id_suffix: false)
      result = lower_case_and_underscored_word.to_s.dup

      inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) }

      result.tr!("_", " ")
      result.lstrip!
      unless keep_id_suffix
        result.delete_suffix!(" id")
      end

      result.gsub!(/([a-z\d]+)/i) do |match|
        match.downcase!
        inflections.acronyms[match] || match
      end

      if capitalize
        result.sub!(/\A\w/) do |match|
          match.upcase!
          match
        end
      end

      result
    end
  end
end

%w(foo foo_bar_id ____foo_bar).each do |str|
  puts "== Comparing with #{str.inspect} (#{RUBY_VERSION}) =="
  unless ActiveSupport::Inflector.humanize(str) == ActiveSupport::Inflector.humanize2(str)
    raise "#{ActiveSupport::Inflector.humanize2(str)} != #{ActiveSupport::Inflector.humanize(str)}"
  end

  Benchmark.ips do |x|
    x.report('humanize') { ActiveSupport::Inflector.humanize(str) }
    x.report('humanize2') { ActiveSupport::Inflector.humanize2(str) }
    x.compare!
  end
  puts
end
```

```
== Comparing with "foo" (2.7.2) ==
Warming up --------------------------------------
            humanize    25.593k i/100ms
           humanize2    29.256k i/100ms
Calculating -------------------------------------
            humanize    263.989k (± 1.9%) i/s -      1.331M in   5.043110s
           humanize2    299.883k (± 2.2%) i/s -      1.521M in   5.075478s

Comparison:
           humanize2:   299882.5 i/s
            humanize:   263989.1 i/s - 1.14x  (± 0.00) slower

== Comparing with "foo_bar_id" (2.7.2) ==
Warming up --------------------------------------
            humanize    18.187k i/100ms
           humanize2    25.678k i/100ms
Calculating -------------------------------------
            humanize    183.702k (± 1.5%) i/s -    927.537k in   5.050326s
           humanize2    250.470k (± 2.5%) i/s -      1.258M in   5.026682s

Comparison:
           humanize2:   250469.6 i/s
            humanize:   183702.3 i/s - 1.36x  (± 0.00) slower

== Comparing with "____foo_bar" (2.7.2) ==
Warming up --------------------------------------
            humanize    18.577k i/100ms
           humanize2    24.686k i/100ms
Calculating -------------------------------------
            humanize    188.868k (± 1.5%) i/s -    947.427k in   5.017524s
           humanize2    255.650k (± 1.8%) i/s -      1.284M in   5.022833s

Comparison:
           humanize2:   255649.8 i/s
```
@kaspth kaspth merged commit f43e701 into rails:main Feb 2, 2021
@kaspth
Copy link
Contributor

kaspth commented Feb 2, 2021

Sweet!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants