From 3e172d6db75ecb8357f807d99f5ad8826e08b771 Mon Sep 17 00:00:00 2001 From: Edouard CHIN Date: Sat, 21 Mar 2026 01:54:14 +0100 Subject: [PATCH] Check happy path first when comparing gem version: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - During resolution, Gem::Version are compared against each other. Since comparing versions is a very hot path we can micro optimize it to check the happy path first. The speed gain on the overall resolution isn't significant but the ips gain is quite substantial. The diff chunk is small so I figure it's worth it anyway. ```ruby a = Gem::Version.new("5.3.1") b = Gem::Version.new("5.3.1") Benchmark.ips do |x| x.report("equal regular:") { a <=> c } x.report("equal optimized:") { a <=> c } x.hold!("equal_temp_results") x.compare!(order: :baseline) end ``` ``` Warming up -------------------------------------- equal optimized: 1.268M i/100ms Calculating ------------------------------------- equal optimized: 12.738M (± 1.5%) i/s (78.50 ns/i) - 64.680M in 5.078754s Comparison: equal regular:: 9866605.0 i/s equal optimized:: 12738310.3 i/s - 1.29x faster ``` ruby 3.4.8 (2025-12-17 revision 995b59f666) +PRISM [arm64-darwin25] --- lib/rubygems/version.rb | 84 ++++++++++++++++++++--------------------- 1 file changed, 41 insertions(+), 43 deletions(-) diff --git a/lib/rubygems/version.rb b/lib/rubygems/version.rb index b7966c3973f7..6e286bd628f0 100644 --- a/lib/rubygems/version.rb +++ b/lib/rubygems/version.rb @@ -345,60 +345,58 @@ def approximate_recommendation # other types may raise an exception. def <=>(other) - if String === other - return unless self.class.correct?(other) - return self <=> self.class.new(other) - end - - return unless Gem::Version === other - - # Fast path for comparison when available. - if @sort_key && other.sort_key - return @sort_key <=> other.sort_key - end - - return 0 if @version == other.version || canonical_segments == other.canonical_segments + if Gem::Version === other + # Fast path for comparison when available. + if @sort_key && other.sort_key + return @sort_key <=> other.sort_key + end - lhsegments = canonical_segments - rhsegments = other.canonical_segments + return 0 if @version == other.version || canonical_segments == other.canonical_segments - lhsize = lhsegments.size - rhsize = rhsegments.size - limit = (lhsize > rhsize ? rhsize : lhsize) + lhsegments = canonical_segments + rhsegments = other.canonical_segments - i = 0 + lhsize = lhsegments.size + rhsize = rhsegments.size + limit = (lhsize > rhsize ? rhsize : lhsize) - while i < limit - lhs = lhsegments[i] - rhs = rhsegments[i] - i += 1 + i = 0 - next if lhs == rhs - return -1 if String === lhs && Numeric === rhs - return 1 if Numeric === lhs && String === rhs + while i < limit + lhs = lhsegments[i] + rhs = rhsegments[i] + i += 1 - return lhs <=> rhs - end + next if lhs == rhs + return -1 if String === lhs && Numeric === rhs + return 1 if Numeric === lhs && String === rhs - lhs = lhsegments[i] + return lhs <=> rhs + end - if lhs.nil? - rhs = rhsegments[i] + lhs = lhsegments[i] - while i < rhsize - return 1 if String === rhs - return -1 unless rhs.zero? - rhs = rhsegments[i += 1] - end - else - while i < lhsize - return -1 if String === lhs - return 1 unless lhs.zero? - lhs = lhsegments[i += 1] + if lhs.nil? + rhs = rhsegments[i] + + while i < rhsize + return 1 if String === rhs + return -1 unless rhs.zero? + rhs = rhsegments[i += 1] + end + else + while i < lhsize + return -1 if String === lhs + return 1 unless lhs.zero? + lhs = lhsegments[i += 1] + end end - end - 0 + 0 + elsif String === other + return unless self.class.correct?(other) + self <=> self.class.new(other) + end end # remove trailing zeros segments before first letter or at the end of the version