Skip to content

Commit f78d45d

Browse files
committed
Fewer allocations in gem installation
For now, on a small rails app I have hanging around: ``` ==> memprof.after.txt <== Total allocated: 872.51 MB (465330 objects) Total retained: 40.48 kB (326 objects) ==> memprof.before.txt <== Total allocated: 890.79 MB (1494026 objects) Total retained: 40.40 kB (328 objects) ``` Not a huge difference in memory usage, but it's a drastic improvement in total number of allocations. Additionally, this will pay huge dividends once ruby/zlib#61 is merged, as it will allow us to completely avoid allocations in the repeated calls to readpartial, which currently accounts for most of the memory usage shown above.
1 parent 5fc69f5 commit f78d45d

File tree

3 files changed

+37
-31
lines changed

3 files changed

+37
-31
lines changed

lib/rubygems/installer.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -224,11 +224,11 @@ def check_executable_overwrite(filename) # :nodoc:
224224

225225
File.open generated_bin, "rb" do |io|
226226
line = io.gets
227-
shebang = /^#!.*ruby/
227+
shebang = /^#!.*ruby/o
228228

229229
# TruffleRuby uses a bash prelude in default launchers
230230
if load_relative_enabled? || RUBY_ENGINE == "truffleruby"
231-
until line.nil? || line =~ shebang do
231+
until line.nil? || shebang.match?(line) do
232232
line = io.gets
233233
end
234234
end
@@ -239,7 +239,7 @@ def check_executable_overwrite(filename) # :nodoc:
239239

240240
# TODO: detect a specially formatted comment instead of trying
241241
# to find a string inside Ruby code.
242-
next unless io.gets.to_s.include?("This file was generated by RubyGems")
242+
next unless io.gets&.include?("This file was generated by RubyGems")
243243

244244
ruby_executable = true
245245
existing = io.read.slice(/

lib/rubygems/package.rb

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -357,18 +357,21 @@ def contents
357357

358358
def digest(entry) # :nodoc:
359359
algorithms = if @checksums
360-
@checksums.keys
361-
else
362-
[Gem::Security::DIGEST_NAME].compact
360+
@checksums.to_h {|algorithm, _| [algorithm, Gem::Security.create_digest(algorithm)] }
361+
elsif Gem::Security::DIGEST_NAME
362+
{ Gem::Security::DIGEST_NAME => Gem::Security.create_digest(Gem::Security::DIGEST_NAME) }
363363
end
364364

365-
algorithms.each do |algorithm|
366-
digester = Gem::Security.create_digest(algorithm)
367-
368-
digester << entry.readpartial(16_384) until entry.eof?
365+
return @digests if algorithms.nil? || algorithms.empty?
369366

370-
entry.rewind
367+
buf = String.new(capacity: 16_384, encoding: Encoding::BINARY)
368+
until entry.eof?
369+
entry.readpartial(16_384, buf)
370+
algorithms.each_value {|digester| digester << buf }
371+
end
372+
entry.rewind
371373

374+
algorithms.each do |algorithm, digester|
372375
@digests[algorithm][entry.full_name] = digester
373376
end
374377

@@ -437,8 +440,6 @@ def extract_tar_gz(io, destination_dir, pattern = "*") # :nodoc:
437440

438441
FileUtils.rm_rf destination
439442

440-
mkdir_options = {}
441-
mkdir_options[:mode] = dir_mode ? 0o755 : (entry.header.mode if entry.directory?)
442443
mkdir =
443444
if entry.directory?
444445
destination
@@ -447,7 +448,7 @@ def extract_tar_gz(io, destination_dir, pattern = "*") # :nodoc:
447448
end
448449

449450
unless directories.include?(mkdir)
450-
FileUtils.mkdir_p mkdir, **mkdir_options
451+
FileUtils.mkdir_p mkdir, mode: dir_mode ? 0o755 : (entry.header.mode if entry.directory?)
451452
directories << mkdir
452453
end
453454

@@ -707,6 +708,7 @@ def verify_files(gem)
707708

708709
def verify_gz(entry) # :nodoc:
709710
Zlib::GzipReader.wrap entry do |gzio|
711+
# TODO: read into a buffer once zlib supports it
710712
gzio.read 16_384 until gzio.eof? # gzip checksum verification
711713
end
712714
rescue Zlib::GzipFile::Error => e

lib/rubygems/package/tar_header.rb

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,8 @@ def self.from(stream)
127127
end
128128

129129
def self.strict_oct(str)
130-
return str.strip.oct if /\A[0-7]*\z/.match?(str.strip)
130+
str.strip!
131+
return str.oct if /\A[0-7]*\z/.match?(str)
131132

132133
raise ArgumentError, "#{str.inspect} is not an octal string"
133134
end
@@ -137,7 +138,8 @@ def self.oct_or_256based(str)
137138
# \ff flags a negative 256-based number
138139
# In case we have a match, parse it as a signed binary value
139140
# in big-endian order, except that the high-order bit is ignored.
140-
return str.unpack("N2").last if /\A[\x80\xff]/n.match?(str)
141+
142+
return str.unpack1("@4N") if /\A[\x80\xff]/n.match?(str)
141143
strict_oct(str)
142144
end
143145

@@ -149,21 +151,23 @@ def initialize(vals)
149151
raise ArgumentError, ":name, :size, :prefix and :mode required"
150152
end
151153

152-
vals[:uid] ||= 0
153-
vals[:gid] ||= 0
154-
vals[:mtime] ||= 0
155-
vals[:checksum] ||= ""
156-
vals[:typeflag] = "0" if vals[:typeflag].nil? || vals[:typeflag].empty?
157-
vals[:magic] ||= "ustar"
158-
vals[:version] ||= "00"
159-
vals[:uname] ||= "wheel"
160-
vals[:gname] ||= "wheel"
161-
vals[:devmajor] ||= 0
162-
vals[:devminor] ||= 0
163-
164-
FIELDS.each do |name|
165-
instance_variable_set "@#{name}", vals[name]
166-
end
154+
@checksum = vals[:checksum] || ""
155+
@devmajor = vals[:devmajor] || 0
156+
@devminor = vals[:devminor] || 0
157+
@gid = vals[:gid] || 0
158+
@gname = vals[:gname] || "wheel"
159+
@linkname = vals[:linkname]
160+
@magic = vals[:magic] || "ustar"
161+
@mode = vals[:mode]
162+
@mtime = vals[:mtime] || 0
163+
@name = vals[:name]
164+
@prefix = vals[:prefix]
165+
@size = vals[:size]
166+
@typeflag = vals[:typeflag]
167+
@typeflag = "0" if @typeflag.nil? || @typeflag.empty?
168+
@uid = vals[:uid] || 0
169+
@uname = vals[:uname] || "wheel"
170+
@version = vals[:version] || "00"
167171

168172
@empty = vals[:empty]
169173
end

0 commit comments

Comments
 (0)