Profiling `require "bundler/setup"` in a 450-gem app: 13 findings, a series of small PRs
Maintainer thường phản hồi trong vòng 1 ngày
@linyiru đang làm issue này rồi.
Từ ngày 31/10/2026.
Đánh giá
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức phù hợp với người mới
- 15/100
- Loại issue
- Tái cấu trúc
- Độ rõ ràng
- Khá rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Công nghệ
- ruby
- Lĩnh vực
- performance, tooling
Hướng nghiên cứu
This is a proposal bundle, not one task: it names 13 independent boot-time changes, most in Bundler's setup path, plus RubyGems' load_defaults and Gem.register_default_spec. Start with one named method such as SpecSet#validate_deps or LockfileParser, and run require "bundler/setup" under a profiler to confirm the cost. Done means the spec for that area still passes and the timing improves, but the maintainer has not yet said which of these changes they want.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
I profiled the boot of a Rails application with 450 activated gems (1,500-line lockfile, 443 installed gemspecs, a few path: and git: gems) and found 13 places in RubyGems and Bundler where boot does work it does not need. I'm about to open several small PRs at once, so this issue explains them as a set and gives you one place to say no to any of them.
Summary
Measured on ruby/rubygems master (c2380f6), Ruby 3.4.12, macOS arm64, hyperfine with the three variants interleaved in one run (min of 50 runs):
require "bundler/setup" |
vs master | |
|---|---|---|
| master | 142.9 ms | |
| the 8 PRs below that I consider free, merged together | 114.3 ms | −20% |
| plus the 4 changes I'm not sending (see below) | 107.8 ms | −25% |
ruby -e 1 itself (with RubyGems) goes from 26.0 to 25.3 ms with the one RubyGems startup change.
Every PR keeps behaviour the same, adds a spec or test, and was checked to fail without the change where that is possible. I ran spec/bundler, spec/runtime, spec/lock, the install/exec/binstubs/help command specs, and the affected RubyGems tests with each change and with all of them merged.
PRs, each independent
No cost, I believe: simpler or equal code.
| PR | Change | Measured |
|---|---|---|
| #9997 | Bundler::StubSpecification#contains_requirable_file? delegates to the stub instead of loading the full gemspec through method_missing |
7 find_active_stub_by_path lookups: 54–96 ms → 13 ms (matters to gems that call it, such as ffi-compiler ≤ 1.4.2) |
| #9999 | SpecSet#validate_deps looks dependencies up by name instead of names.include? (a fresh array per call) and none? over every spec |
9.8 → 2.6 ms |
| #10000 | add_default_gems_to checks the stub's name before loading the full default gemspec |
replace_entrypoints 8.4 → 5.8 ms |
| #10001 | Autoload the vendored FileUtils instead of requiring it in bundler.rb |
−2.9 ms |
| #10002 | Gem::Requirement.default builds new instead of parsing ">= 0" |
1,144 calls in setup, −0.9 ms |
| #10003 | LockfileParser checks validity with one regexp instead of a second split, and walks lines with each_line |
lockfile parse 13.4 → 10.9 ms |
Small cost, worth it in my view; each PR says what the cost is.
| PR | Change | Measured | Cost |
|---|---|---|---|
| #10004 | Add each lockfile source's spec names once after parsing instead of `Array# | ` per spec line (O(n²)) | lockfile parse 13.1 → 10.0 ms |
| #10005 | Build Bundler::UI::Shell's Thor shell on first output |
−12 ms (23 Thor files no longer loaded by bundler/setup) |
lazy initialization in UI::Shell, with the CLI still building it up front for --no-color; Installer now requires Thor itself |
| #10006 | load_defaults indexes $LOADED_FEATURES once instead of scanning it for each of ~800 default gem files |
ruby -e 1: −0.7 to −1.7 ms, every Ruby process |
an optional argument on Gem.register_default_spec |
Not sending, unless you want them
These change semantics or add state that has to be kept correct, for 1–3 ms each. I'd rather ask than send them:
Gem::Specification.resetkeeps the default gemspecs in the load cache. Bundler callsGem.clear_pathstwice during setup, which drops the 60 default gemspecsload_defaultsjust evaluated, so they are evaluated again (−2.9 ms after theadd_default_gems_toPR). Changes whatresetresets.setup_manpathonly looks at load path entries ending inlib(−1.7 ms on master, which already usesglob_files_in_dir). Changes which entries are scanned.Source::Git#revisionreturns the locked revision without building aGitProxy(−2 ms, 4 files). CouplesSource::Gitto howGitProxytreats a given revision, and skips the ambiguous-ref check at runtime.- Memoize
RubygemsIntegration#default_stubs, which reads the default specifications directory a second and third time (−1.1 ms). A new cache to invalidate.
Larger costs I did not try to fix
source "https://rubygems.org"in the Gemfile makesnormalize_uriload the vendored URI library, 16 files, 6–8 ms, on every boot that never fetches anything.remotesare URI objects compared and hashed in several places, so making this lazy is a design change.- Bundler reads the stub of every installed gem (443 here, 13–14 ms, of which ~6.4 ms is the
open/readlinefloor) althoughbundler/setupneeds only the locked ones. - Every Ruby process evaluates the ~60 default gemspecs in
load_defaults(~5.3 ms) to build the require-path map.
Happy to adjust any of the PRs, or to close the ones you'd rather not take.
- Ngôn ngữ chính
- Ruby
- Star
- 4k
- Fork
- 2k
- Merge trung bình
- 2 ngày 33 phút
- Pull request đã merge (30 ngày)
- 50
Chuẩn bị môi trường
Khởi chạy dev container của dự án ngay trên trình duyệt, bằng tài khoản GitHub của bạn.
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của ruby/rubygems
-
Bundler 4.0.x fails on Ruby 4.1 when .bundle/config is "---"Có thể đã có người làm Có pull request liên kết đang mở hoặc đã được merge. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 73/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Cooldown drops the locked version of a gem named in bundle lock --update or bundle updateCó thể đã có người làm @senid231 đã nhận 5 ngày trước. Đang mở
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 35/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Gem::Package::TarReader silently ignores pax/GNU extension headersCó thể đã có người làm @akasakariko đã nhận 9 ngày trước. Đang mởRubyGems
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 55/100
ruby/rubygems#9988 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
`Gem::Version.new`, `#bump`, and `#release` do not work in ractorCó thể đã có người làm @mmporong đã nhận 13 ngày trước. Đang mởRubyGems
Độ khó 3/5 Nửa ngày Mức phù hợp với người mới 55/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Gem.load_plugins loads same plugin multiple times from different Gem.path entriesCó thể đã có người làm @sourya2704 đã nhận 14 ngày trước. Đang mở
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 25/100
ruby/rubygems#9945 · 4 bình luận · 3 reaction ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của ruby/rubygems
Issue tương tự
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
swombat/souls-house#842 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
hanami/hanami-cli#476 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
jdx/mise#15471 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
activerecord-hackery/ransack#1799 · 2 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
rubys/roundhouse#1950 ·