Skip to content

Commit

Permalink
bundled_gems.rb: Add a fast path
Browse files Browse the repository at this point in the history
[Bug #20641] `Gem::BUNDLED_GEMS.warning?` adds a lot of extra
work on top of `require`. When the call end up atually loading code
the overhead is somewhat marginal.

However it's not uncommon for code to go some late `require` in some
paths, so it's expected that calling `require` with something already
required is somewhat fast, and `bundled_gems.rb` breaks this assumption.

To avoid this, we can have a fast path that in most case allow to
short-circuit all the heavy computations. If we extract the feature
basename and it doesn't match any of the bundled gems we care about
we can return very early.

With this change `require 'date'` is now only 1.33x slower on Ruby
3.3.3, than it was on Ruby 3.2.2, whereas before this change it
was at least 100x slower.
  • Loading branch information
byroot committed Jul 22, 2024
1 parent 3a16971 commit 82aee1a
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 0 deletions.
9 changes: 9 additions & 0 deletions lib/bundled_gems.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ module Gem::BUNDLED_GEMS
"logger" => "3.5.0",
}.freeze

SINCE_FAST_PATH = SINCE.transform_keys { |g| g.sub(/\A.*\-/, "") }.freeze

EXACT = {
"kconv" => "nkf",
}.freeze
Expand Down Expand Up @@ -92,6 +94,13 @@ def self.find_gem(path)
def self.warning?(name, specs: nil)
# name can be a feature name or a file path with String or Pathname
feature = File.path(name)

if feature.include?("/")
return unless SINCE_FAST_PATH[File.basename(feature, ".*")]
else
return unless SINCE_FAST_PATH[feature]
end

# bootsnap expands `require "csv"` to `require "#{LIBDIR}/csv.rb"`,
# and `require "syslog"` to `require "#{ARCHDIR}/syslog.so"`.
name = feature.delete_prefix(ARCHDIR)
Expand Down
35 changes: 35 additions & 0 deletions test/test_bundled_gems.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
require_relative "rubygems/helper"
require "rubygems"
require "bundled_gems"

class TestBundlerGem < Gem::TestCase
def setup
Gem::BUNDLED_GEMS::WARNED.clear
end

def teardown
Gem::BUNDLED_GEMS::WARNED.clear
end

def test_warning
assert Gem::BUNDLED_GEMS.warning?("rss", specs: {})
assert_nil Gem::BUNDLED_GEMS.warning?("rss", specs: {})
end

def test_no_warning_warning
assert_nil Gem::BUNDLED_GEMS.warning?("some_gem", specs: {})
assert_nil Gem::BUNDLED_GEMS.warning?("/path/to/some_gem.rb", specs: {})
end

def test_warning_libdir
path = File.join(::RbConfig::CONFIG.fetch("rubylibdir"), "rss.rb")
assert Gem::BUNDLED_GEMS.warning?(path, specs: {})
assert_nil Gem::BUNDLED_GEMS.warning?(path, specs: {})
end

def test_warning_archdir
path = File.join(::RbConfig::CONFIG.fetch("rubyarchdir"), "syslog.so")
assert Gem::BUNDLED_GEMS.warning?(path, specs: {})
assert_nil Gem::BUNDLED_GEMS.warning?(path, specs: {})
end
end

0 comments on commit 82aee1a

Please sign in to comment.