[rubygems/rubygems] Optimize allocations in Gem::Version

From running in a random rails app I have locally, here are the changes

1) for `bundle lock --update --bundler` (forcing Bundler to go through
dependency resolution)

```
==> memprof.after.txt <==
Total allocated: 2.98 MB (48307 objects)
Total retained:  1.21 MB (16507 objects)

==> memprof.before.txt <==
Total allocated: 12.62 MB (198506 objects)
Total retained:  1.30 MB (23133 objects)
```

2) for `bin/rails runner true` (essentially only bundler/setup)

```
==> memprof.after.txt <==
Total allocated: 59.50 kB (1017 objects)
Total retained:  25.08 kB (362 objects)

==> memprof.before.txt <==
Total allocated: 561.82 kB (8575 objects)
Total retained:  27.28 kB (513 objects)
```

https://github.com/rubygems/rubygems/commit/35c8ed2cb8
This commit is contained in:
Samuel Giddins 2023-09-18 12:47:01 -07:00 committed by git
parent 5810304c2e
commit bf71b0eda5
2 changed files with 40 additions and 20 deletions

View File

@ -311,8 +311,8 @@ module Gem::QueryUtils
label = "Installed at"
specs.each do |s|
version = s.version.to_s
version << ", default" if s.default_gem?
entry << "\n" << " #{label} (#{version}): #{s.base_dir}"
default = ", default" if s.default_gem?
entry << "\n" << " #{label} (#{version}#{default}): #{s.base_dir}"
label = " " * label.length
end
end

View File

@ -162,7 +162,7 @@ class Gem::Version
# A string representation of this Version.
def version
@version.dup
@version
end
alias_method :to_s, :version
@ -173,7 +173,7 @@ class Gem::Version
def self.correct?(version)
nil_versions_are_discouraged! if version.nil?
!!(version.to_s =~ ANCHORED_VERSION_PATTERN)
ANCHORED_VERSION_PATTERN.match?(version.to_s)
end
##
@ -224,9 +224,17 @@ class Gem::Version
end
# If version is an empty string convert it to 0
version = 0 if version.is_a?(String) && version =~ /\A\s*\Z/
version = 0 if version.is_a?(String) && /\A\s*\Z/.match?(version)
@version = version.to_s.strip.gsub("-",".pre.")
@version = version.to_s
# optimization to avoid allocation when given an integer, since we know
# it's to_s won't have any spaces or dashes
unless version.is_a?(Integer)
@version = @version.strip
@version.gsub!("-",".pre.")
end
@version = -@version
@segments = nil
end
@ -252,7 +260,7 @@ class Gem::Version
# same precision. Version "1.0" is not the same as version "1".
def eql?(other)
self.class === other && @version == other._version
self.class === other && @version == other.version
end
def hash # :nodoc:
@ -284,7 +292,7 @@ class Gem::Version
end
def yaml_initialize(tag, map) # :nodoc:
@version = map["version"]
@version = -map["version"]
@segments = nil
@hash = nil
end
@ -302,7 +310,7 @@ class Gem::Version
def prerelease?
unless instance_variable_defined? :@prerelease
@prerelease = !(@version =~ /[a-zA-Z]/).nil?
@prerelease = /[a-zA-Z]/.match?(version)
end
@prerelease
end
@ -354,7 +362,7 @@ class Gem::Version
return self <=> self.class.new(other) if (String === other) && self.class.correct?(other)
return unless Gem::Version === other
return 0 if @version == other._version || canonical_segments == other.canonical_segments
return 0 if @version == other.version || canonical_segments == other.canonical_segments
lhsegments = canonical_segments
rhsegments = other.canonical_segments
@ -381,10 +389,26 @@ class Gem::Version
end
def canonical_segments
@canonical_segments ||=
_split_segments.map! do |segments|
segments.reverse_each.drop_while {|s| s == 0 }.reverse
end.reduce(&:concat)
@canonical_segments ||= begin
numeric_segments, string_segments = _split_segments
canonical_segments = []
seen_non_zero = false
string_segments.reverse_each do |segment|
if seen_non_zero || (seen_non_zero = (segment != 0))
canonical_segments << segment
end
end
seen_non_zero = false
numeric_segments.reverse_each do |segment|
if seen_non_zero || (seen_non_zero = (segment != 0))
canonical_segments << segment
end
end
canonical_segments.reverse!
canonical_segments.freeze
end
end
def freeze
@ -395,17 +419,13 @@ class Gem::Version
protected
def _version
@version
end
def _segments
# segments is lazy so it can pick up version values that come from
# old marshaled versions, which don't go through marshal_load.
# since this version object is cached in @@all, its @segments should be frozen
@segments ||= @version.scan(/[0-9]+|[a-z]+/i).map do |s|
/^\d+$/.match?(s) ? s.to_i : s
@segments ||= @version.scan(/[0-9]+|[a-z]+/i).map! do |s|
/^\d+$/.match?(s) ? s.to_i : -s
end.freeze
end