Skip sampling threads which did not have the GVL since the last sample#5777
Conversation
|
👋 Hey @eregon, please fill "Change log entry" section in the pull request description. If changes need to be present in CHANGELOG.md you can state it this way **Change log entry**
Yes. A brief summary to be placed into the CHANGELOG.md(possible answers Yes/Yep/Yeah) Or you can opt out like that **Change log entry**
None.(possible answers No/Nope/None) Visited at: 2026-05-19 11:09:47 UTC |
|
✨ Fix all issues with BitsAI or with Cursor
|
d838099 to
694a7bc
Compare
BenchmarksBenchmark execution time: 2026-06-15 11:17:54 Comparing candidate commit ed9e438 in PR branch Found 0 performance improvements and 0 performance regressions! Performance is the same for 48 metrics, 1 unstable metrics.
|
81f223e to
f9f4da7
Compare
|
@ivoanjo This is almost ready for a first pass review but needs tests. |
f9f4da7 to
f000bc7
Compare
06e3149 to
e4fbc56
Compare
Typing analysisThis PR does not change typing compared to the base branch. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 06e3149986
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
ivoanjo
left a comment
There was a problem hiding this comment.
I haven't yet finished going through it all, but here's a first pass before I go for lunch :)
I think in general this looks reasonable and is in the right direction, all my notes are about the details of how we get there ;)
typedef int rb_internal_thread_specific_key_t;
#define RB_INTERNAL_THREAD_SPECIFIC_KEY_MAX 8
/**
* Create a key to store thread specific data.
*
* These APIs are designed for tools using
* rb_internal_thread_event_hook APIs.
*
* Note that only `RB_INTERNAL_THREAD_SPECIFIC_KEY_MAX` keys
* can be created. raises `ThreadError` if exceeded.That's one more reason to not use 2 |
e4fbc56 to
6bb2090
Compare
* Store the per_thread_context in the Ruby Thread TLS (rb_internal_thread_specific_set()), so all the state is in one struct and not a struct + a single field in the TLS. * We'll need more place in TLS for #5777 (comment) but there is a hard limit on the number of TLS vars, so this avoids that problem. * Much simpler and more consistent, the state is together vs spread in different places.
* Store the per_thread_context in the Ruby Thread TLS (rb_internal_thread_specific_set()), so all the state is in one struct and not a struct + a single field in the TLS. * We'll need more place in TLS for #5777 (comment) but there is a hard limit on the number of TLS vars, so this avoids that problem. * Much simpler and more consistent, the state is together vs spread in different places.
* Store the per_thread_context in the Ruby Thread TLS (rb_internal_thread_specific_set()), so all the state is in one struct and not a struct + a single field in the TLS. * We'll need more place in TLS for #5777 (comment) but there is a hard limit on the number of TLS vars, so this avoids that problem. * Much simpler and more consistent, the state is together vs spread in different places.
* Store the per_thread_context in the Ruby Thread TLS (rb_internal_thread_specific_set()), so all the state is in one struct and not a struct + a single field in the TLS. * We'll need more place in TLS for #5777 (comment) but there is a hard limit on the number of TLS vars, so this avoids that problem. * Much simpler and more consistent, the state is together vs spread in different places.
* Store the per_thread_context in the Ruby Thread TLS (rb_internal_thread_specific_set()), so all the state is in one struct and not a struct + a single field in the TLS. * We'll need more place in TLS for #5777 (comment) but there is a hard limit on the number of TLS vars, so this avoids that problem. * Much simpler and more consistent, the state is together vs spread in different places.
|
This is ready for another round of review |
ivoanjo
left a comment
There was a problem hiding this comment.
Gave it another pass! Most of my feedback is very small, the big thing is the bias on the first sample after the period where we skipped.
cbd3d5a to
ba5360b
Compare
ivoanjo
left a comment
There was a problem hiding this comment.
👍 LGTM. One last pass at feedback, none blocking -- e.g. I think it's OK to merge as-is and then address feedback in a follow-up PR.
The only important thing I'd like to raise (it wasn't clear to me) is validating the assumption that a postponed job scheduled inside a RUBY_INTERNAL_THREAD_EVENT_RESUMED callback will always run before the thread gets a chance to move. Just in case as getting that one wrong will mean very subtle bias that will be really hard to spot and really hard to debug.
c63c8a7 to
484b6ea
Compare
ivoanjo
left a comment
There was a problem hiding this comment.
👍 Still good, (almost almost) no further questions, your honor 🎉
180c7cd to
0be0983
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0be09838e4
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
1c5da8c to
51a5f99
Compare
* Since such threads did not get the GVL then their Ruby-level stack must be the same and there is no need to sample them, we can just adjust the duration of the next sample instead. We need to sample when the GVL is acquired again, otherwise we would report an incorrect stack for the sample representing the period without GVL. * Emit a catch-up sample for still-suspended threads before serialization so we still sample at least once per reporting period (60 seconds). * The per-Ruby-Thread state is an integer: - low bit: current state (1 = suspended/no-GVL, 0 = running/has-GVL) - bits 1+: monotonic event counter, incremented on every RESUMED for that Thread * Add benchmark for profiling overhead. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
51a5f99 to
ed9e438
Compare
The counter is global across all threads, so background threads in the test process can also be skipped, making exact equality checks flaky. The per-thread was_skipped_at_last_sample assertions already validate the optimization correctly. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Skipped threads intentionally keep a stale wall_time_at_previous_sample_ns, so exclude them from the assertion that checks all threads have a wall time within the current sample window. Co-Authored-By: Claude Opus 4.6 <[email protected]>
039406e to
04e1673
Compare
serialize! can flush extra samples for skipped background threads. Use sample_for_thread in single_sample to select the expected thread's sample. For #sample_skipped_allocation_samples, filter by alloc-samples since it uses a synthetic thread. Co-Authored-By: Claude Opus 4.6 <[email protected]>
04e1673 to
60714ad
Compare
be the same and there is no need to sample them, we can just adjust the
duration/weight of the next sample instead.
The next sample will have the correct stack because it will be just after
GVL acquire.
so we still sample at least once per upload period (60 seconds).
What does this PR do?
Reduce profiling overhead by skipping redundant samples of threads which did not have the GVL since the last sample.
Motivation:
Reduce profiling overhead.
Change log entry
Reduce profiling overhead for threads which do not hold the GVL.
Additional Notes:
How to test the change?