[pull] master from ruby:master - #1334
Merged
Merged
Conversation
Coverage.peek_result rebuilt the nested branch coverage hash on every
call, and hashing its array keys (Array#hash via the recursion guard)
dominated the cost. Build { base_key => { target_key => counter_index } }
once per file and cache it; each peek dups it and fills in the counters.
40k branch sites: 40 ms -> 3.5 ms per peek. The key arrays are now frozen
and shared between results.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Likewise for method coverage: walking me_set and cme2counter and hashing
the array keys on every peek dominated the cost. Maintain
{ path => { key => me or [me, ...] } } incrementally (both hashes are
append-only) and fill in the counts from cme2counter on each peek.
Extract rb_coverage_method_data_of() from thread.c for that.
10k methods: 24 ms -> 0.7 ms per peek. rack's test suite with SimpleCov
per-test tracking (lines+branches+methods): 30 s -> 8.4 s.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A Ractor whose block ends while one of its own threads is still running took a full second to terminate: rb_thread_terminate_all() sleeps for a second per round and relies on the last sub-thread to wake it, but that wakeup was sent only when the Ractor's main thread already had THREAD_KILLED, which thread_start_func_2 sets after rb_thread_terminate_all() returns. For the main Ractor rb_ec_cleanup() sets it beforehand, which is why only Ractors were affected. Setting THREAD_KILLED earlier is not an option here: thread_sched_switch() reads it as to_dead, promising the M:N scheduler that the coroutine is never resumed, and this thread still parks and resumes inside the wait. So mark the wait itself instead: rb_thread_terminate_all() sets threads.terminating before it waits, and the exiting thread wakes `main` on that. Nothing clears it afterwards, because no thread of that Ractor runs again; the Ractor that survives a fork gets it reset along with the rest of the thread set.
…f net-http-persistent Bundler's metadata transport was the last consumer of the vendored net-http-persistent. Hand out configured Gem::Net::HTTP connections from a small per-host pool compatible with Gem::Request's checkout/checkin interface, so metadata requests share RubyGems' request execution with .gem downloads. As a side effect, https_proxy is now honored for https sources, and a configured :no_proxy no longer falls back to the environment proxy. ruby/rubygems@b6499513b9 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Nothing references them since Bundler metadata fetching moved to Gem::Request. connection_pool was only vendored as a dependency of net-http-persistent, so it goes away too, along with their automatiek entries and patches. ruby/rubygems@eaf49e1329 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…l state The before hook skips before it saves the previous connection pool client and UI, but the after hook restored those unconditionally, leaving Gem::Request::ConnectionPools.client as nil for every later example. Only restore what was captured, matching the guard Artifice.deactivate uses. ruby/rubygems@4cf080cb0a Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The test reads GC.stat and GC.stat_heap separately and asserts that the per-heap values sum to the totals. Both come from the same counters (objspace_live_slots() is the sum of the per-heap live slots, and so on), so they only disagree when something allocated or freed between the two reads: TestGc#test_stat_heap_constraints [test/ruby/test_gc.rb:298]: <243163> expected but was <243164>. Running the pair in a loop with a background allocating thread reproduces it, 7 mismatches in 200,000 rounds; reading the pair again leaves 0. Disabling GC does not help (7 in 200,000): the reads disagree over an allocation, not over a collection. So retry the test once instead of leaving it flaky. An accounting bug fails the retry too, so this keeps what the test checks. Seen on YJIT macOS with --yjit-call-threshold=1: https://github.com/ruby/ruby/actions/runs/32472080033/job/96740787745
ZJIT: infer_types early exit if no back traversals
Function::infer_types repeatedly loops over all blocks and instructions
in Reverse Post Order (RPO), progressively updating the types. The loop
continues until a fixpoint is reached (no further type changes will
occur).
This patch improves performance by adding an early exit if no back edge
traversals have occurred. If there were no back traversals the fixpoint
has already been reached, and continuing to loop will not change the
result.
Note, I'm considering an edge that links a block back to itself as a
back edge. The block can loop with different arguments, causing further
type propagation.
This patch reduces compile_hir_time from ~960ms to ~880ms.
Before:
% for i in $(seq 0 5); do WARMUP_ITRS=0 MIN_BENCH_ITRS=2 MIN_BENCH_TIME=0 ./ruby --zjit --zjit-stats ruby-bench/benchmarks/lobsters/benchmark.rb 2>&1 | grep compile_hir_time; done
compile_hir_time: 949ms
compile_hir_time: 962ms
compile_hir_time: 975ms
compile_hir_time: 976ms
compile_hir_time: 952ms
compile_hir_time: 951ms
After:
% for i in $(seq 0 5); do WARMUP_ITRS=0 MIN_BENCH_ITRS=2 MIN_BENCH_TIME=0 ./ruby --zjit --zjit-stats ruby-bench/benchmarks/lobsters/benchmark.rb 2>&1 | grep compile_hir_time; done
compile_hir_time: 866ms
compile_hir_time: 881ms
compile_hir_time: 881ms
compile_hir_time: 894ms
compile_hir_time: 897ms
compile_hir_time: 862ms
Ractor::Port#receive, Ractor.receive and Ractor.select gain a `timeout:` keyword and return nil when it passes. The wait itself stays where it is: rb_ractor_sched_wait() keeps parking the thread in the thread scheduler, so an M:N thread still hands its native thread back instead of becoming dedicated. How the deadline is taken depends on which kind of thread waits: A dedicated native thread parks on its own condvar, so it takes the deadline there, the way native_cond_sleep() does. Nothing else is involved: the condvar it waits on is the one a send already signals. An M:N thread has no condvar of its own, so its deadline is armed on the timer thread as a timeout-only wheel entry, and the timer thread wakes it through thread_sched_to_ready_common(), which is exactly how rb_ractor_sched_wakeup() wakes it for a send. That gives such a waiter two wakers, so rb_ractor_sched_wakeup() now takes an armed timeout back before waking, skips a thread that a fired timeout already made runnable, and bumps the scheduler event serial so a timeout that has not fired cannot wake it a second time. `timeout: 0` is a poll: it never blocks, converts nothing and reads no clock, so it costs what a receive of a waiting message costs. It still delivers the incoming queue first, so a message another thread just sent is not missed. A timeout that expires leaves the waiter on the Ractor's waiter list, as a spurious wakeup does, and ractor_wait_receive() still delivers pending messages before it reports the timeout, so a message that arrived just before the deadline is not lost. Both kinds of wait exist on every pthread platform, including builds without the timer wheel (USE_MN_THREADS == 0), where every thread is dedicated. On win32 the wait is a condvar wait, which takes the timeout directly.
Prepending a module to a module that already has includers backfills an
origin iclass into each includer's ancestor chain using rb_prepend_module.
The backfilled iclass is never added to a subclasses list, so
rb_clear_method_cache can't reach it when one of the module's methods is
later redefined.
Calling super through a call site that has cached the backfilled iclass
calls the old entry, even though it should be redefined.
```ruby
module M; def foo; :m; end; end
class D; include M; end
M.prepend(Module.new { def foo; super; end })
D.new.foo # prime the super cache
M.send(:define_method, :foo) { :hooked }
D.new.foo # => got :m, expected :hooked
```
This commit makes sure that the backfilled iclass is registered in the
subclasses list.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
See Commits and Changes for more details.
Created by
pull[bot] (v2.0.0-alpha.4)
Can you help keep this open source service alive? 💖 Please sponsor : )