Skip to content

Commit f2dfbd6

Browse files
committed
Optimize String#parameterize
It can use a codepath where it doesn't have to do regex stuff: ```rb require "benchmark/ips" require "active_support" require "active_support/inflector/transliterate" CASES = [ "Donald E. Knuth" , "Random text with *(bad)* characters", "With-some-dashes" , "Retain_underscore" , "Trailing bad characters!@#" , "!@#Leading bad characters" , "Squeeze separators" , "Test with + sign" , "Test with malformed utf8 \251" , ] def parameterize_old(string, separator: "-", preserve_case: false, locale: nil) # Replace accented chars with their ASCII equivalents. parameterized_string = ActiveSupport::Inflector.transliterate(string) # Turn unwanted chars into the separator. parameterized_string.gsub!(/[^a-z0-9\-_]+/i, separator) unless separator.nil? || separator.empty? if separator == "-" re_duplicate_separator = /-{2,}/ re_leading_trailing_separator = /^-|-$/i else re_sep = Regexp.escape(separator) re_duplicate_separator = /#{re_sep}{2,}/ re_leading_trailing_separator = /^#{re_sep}|#{re_sep}$/i end # No more than one of the separator in a row. parameterized_string.gsub!(re_duplicate_separator, separator) # Remove leading/trailing separator. parameterized_string.gsub!(re_leading_trailing_separator, "") end parameterized_string.downcase! unless preserve_case parameterized_string end def parameterize_new(string, separator: "-", preserve_case: false, locale: nil) # Replace accented chars with their ASCII equivalents. parameterized_string = ActiveSupport::Inflector.transliterate(string) # Turn unwanted chars into the separator. parameterized_string.gsub!(/[^a-z0-9\-_]+/i, separator) unless separator.nil? || separator.empty? # No more than one of the separator in a row. if separator.length == 1 parameterized_string.squeeze!(separator) else re_sep = Regexp.escape(separator) parameterized_string.gsub!(/#{re_sep}{2,}/, separator) end # Remove leading/trailing separator. parameterized_string.delete_prefix!(separator) parameterized_string.delete_suffix!(separator) end parameterized_string.downcase! unless preserve_case parameterized_string end puts "DEFAULT SEPARATOR" Benchmark.ips do |x| x.report("old") do CASES.each { parameterize_old(_1) } end x.report("new") do CASES.each { parameterize_new(_1) } end x.compare! end puts "OTHER SEPARATOR LENGTH ONE" Benchmark.ips do |x| x.report("old") do CASES.each { parameterize_old(_1, separator: "_") } end x.report("new") do CASES.each { parameterize_new(_1, separator: "_") } end x.compare! end puts "OTHER SEPARATOR LENGTH TWO" Benchmark.ips do |x| x.report("old") do CASES.each { parameterize_old(_1, separator: "__") } end x.report("new") do CASES.each { parameterize_new(_1, separator: "__") } end x.compare! end ``` ``` $ ruby test.rb DEFAULT SEPARATOR ruby 3.4.2 (2025-02-15 revision d2930f8e7a) +PRISM [x86_64-linux] Warming up -------------------------------------- old 2.733k i/100ms new 3.278k i/100ms Calculating ------------------------------------- old 27.335k (± 1.3%) i/s (36.58 μs/i) - 139.383k in 5.099871s new 32.694k (± 0.8%) i/s (30.59 μs/i) - 163.900k in 5.013461s Comparison: new: 32694.3 i/s old: 27335.4 i/s - 1.20x slower OTHER SEPARATOR LENGTH ONE ruby 3.4.2 (2025-02-15 revision d2930f8e7a) +PRISM [x86_64-linux] Warming up -------------------------------------- old 1.295k i/100ms new 3.273k i/100ms Calculating ------------------------------------- old 12.901k (± 1.9%) i/s (77.51 μs/i) - 64.750k in 5.020950s new 32.656k (± 1.4%) i/s (30.62 μs/i) - 163.650k in 5.012471s Comparison: new: 32655.6 i/s old: 12901.1 i/s - 2.53x slower OTHER SEPARATOR LENGTH TWO ruby 3.4.2 (2025-02-15 revision d2930f8e7a) +PRISM [x86_64-linux] Warming up -------------------------------------- old 1.189k i/100ms new 1.888k i/100ms Calculating ------------------------------------- old 11.908k (± 0.4%) i/s (83.98 μs/i) - 60.639k in 5.092317s new 18.813k (± 1.0%) i/s (53.16 μs/i) - 94.400k in 5.018446s Comparison: new: 18812.7 i/s old: 11908.1 i/s - 1.58x slower ```
1 parent 7a031e3 commit f2dfbd6

File tree

1 file changed

+6
-8
lines changed

1 file changed

+6
-8
lines changed

activesupport/lib/active_support/inflector/transliterate.rb

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -128,18 +128,16 @@ def parameterize(string, separator: "-", preserve_case: false, locale: nil)
128128
parameterized_string.gsub!(/[^a-z0-9\-_]+/i, separator)
129129

130130
unless separator.nil? || separator.empty?
131-
if separator == "-"
132-
re_duplicate_separator = /-{2,}/
133-
re_leading_trailing_separator = /^-|-$/i
131+
# No more than one of the separator in a row.
132+
if separator.length == 1
133+
parameterized_string.squeeze!(separator)
134134
else
135135
re_sep = Regexp.escape(separator)
136-
re_duplicate_separator = /#{re_sep}{2,}/
137-
re_leading_trailing_separator = /^#{re_sep}|#{re_sep}$/i
136+
parameterized_string.gsub!(/#{re_sep}{2,}/, separator)
138137
end
139-
# No more than one of the separator in a row.
140-
parameterized_string.gsub!(re_duplicate_separator, separator)
141138
# Remove leading/trailing separator.
142-
parameterized_string.gsub!(re_leading_trailing_separator, "")
139+
parameterized_string.delete_prefix!(separator)
140+
parameterized_string.delete_suffix!(separator)
143141
end
144142

145143
parameterized_string.downcase! unless preserve_case

0 commit comments

Comments
 (0)