Feature #13750
openImprove String#casecmp? and Symbol#casecmp? performance with ASCII string
Description
I think String#casecmp and String#casecmp? are similar methods. But they have different performance with ASCII strings.
It seems that String#casecmp handles ASCII string only, but it is faster than String#casecmp?.
This patch uses the code of String#casecmp on String#casecmp? for ASCII strings. However, it introduces a minor penalty for UTF8 strings due to detection of ASCII/UTF8 strings.
String#casecmp? ASCII -> 61.3 % up
String#casecmp? UTF8 -> 1.3 % down
Symbol#casecmp? ASCII -> 80.0 % up
Symbol#casecmp? UTF8 -> 4.0 % down
Before¶
Calculating -------------------------------------
String#casecmp 5.961M (± 3.8%) i/s - 29.838M in 5.017907s
String#casecmp? ASCII
3.530M (± 8.6%) i/s - 17.554M in 5.034848s
String#casecmp? UTF8 1.252M (± 7.4%) i/s - 6.213M in 5.012168s
Symbol#casecmp 8.555M (± 2.4%) i/s - 42.822M in 5.009280s
Symbol#casecmp? ASCII
4.235M (± 9.7%) i/s - 20.824M in 5.001368s
Symbol#casecmp? UTF8 1.329M (± 0.1%) i/s - 6.704M in 5.043725s
After¶
Calculating -------------------------------------
String#casecmp 5.984M (± 6.4%) i/s - 29.829M in 5.020331s
String#casecmp? ASCII
5.658M (± 1.5%) i/s - 28.308M in 5.004547s
String#casecmp? UTF8 1.215M (± 4.3%) i/s - 6.132M in 5.060292s
Symbol#casecmp 8.651M (± 0.9%) i/s - 43.313M in 5.007215s
Symbol#casecmp? ASCII
7.462M (± 0.5%) i/s - 37.489M in 5.023892s
Symbol#casecmp? UTF8 1.275M (± 0.2%) i/s - 6.444M in 5.052743s
Test code¶
require 'benchmark/ips'
Benchmark.ips do |x|
x.report "String#casecmp" do |loop|
loop.times { "aBcDeF".casecmp("abcdefg") }
end
x.report "String#casecmp? ASCII" do |loop|
loop.times { "aBcDeF".casecmp?("abcdefg") }
end
x.report "String#casecmp? UTF8" do |loop|
loop.times { "\u{e4 f6 fc}".casecmp?("\u{c4 d6 dc}") }
end
x.report "Symbol#casecmp" do |loop|
loop.times { :aBcDeF.casecmp(:abcdefg) }
end
x.report "Symbol#casecmp? ASCII" do |loop|
loop.times { :aBcDeF.casecmp?(:abcdefg) }
end
x.report "Symbol#casecmp? UTF8" do |loop|
loop.times { :"\u{e4 f6 fc}".casecmp?(:"\u{c4 d6 dc}") }
end
end
Patch¶
Updated by watson1978 (Shizuo Fujita) over 7 years ago
Because String#casecmp? duplicates object at rb_str_downcase()
every time, so String#casecmp? is slower than String#casecmp?
Updated by jeremyevans0 (Jeremy Evans) over 5 years ago
- Tracker changed from Bug to Feature
- Backport deleted (
2.2: UNKNOWN, 2.3: UNKNOWN, 2.4: UNKNOWN)
Updated by the_spectator (Akshay Birajdar) over 4 years ago
@koic (Koichi ITO) Made a new attempt with patch https://github.com/ruby/ruby/pull/2941
Updated by naruse (Yui NARUSE) almost 4 years ago
When you avoid that case, you have a option around coderange: coderange is a cached information whether the string contains (1) only ASCII 7 bit characters (2) also has 8 bit characters (3) broken byte sequence (4) unknown. Some strings are already scanned its coderange and caches it in a string object, but others are not. Whether this casecmp? optimization uses the cache and not scan string if the cache doesn't exist, or scan if it doesn't have a cache. If you use the cache, I wonder whether strings in real applications have cache or not. If you scan, I wonder if it still gets faster.
Imagine casecmp?
with following chatacters:
- "a" * 100000 + "A"
- "a" * 100000 + "a"
- "a" * 100000 + "À"
- "a" * 100000 + "à"
- "ab"
Usingrb_enc_str_asciionly_p
enforces to scan whole strings if it doesn't have coderange cache, and it can be overhead.
To avoid such trade off, I think you need to implement integrated casecmp with rb_str_casemap.