Reduce virtual-thread context-propagation overhead on park/unpark#11893
Reduce virtual-thread context-propagation overhead on park/unpark#11893amarziali wants to merge 3 commits into
Conversation
This comment has been minimized.
This comment has been minimized.
🟢 Java Benchmark SLOs — All performance SLOs passed
PR vs. master results
Commit: Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion. |
|
As discussed the inefficiency in We have to do this because all we know is that the caller will eventually pass that same context back into swap (that's the essential contract of this call.) It's also why we need to create a new The upcoming Given this I think it's valid to optimize the VirtualThread instrumentation in the short-term to avoid using |
d80872e to
b05f030
Compare
Sphinx Review(as we were discussing during the sync meeting, using this generated PR as exemple to demo the output) MEDIUM (9)
LOW (5)
Bottom line: the core idea (seed-once instead of swap-every-park/unpark) is sound and the benchmarks back up the perf claim, but #7 (silently dropping restore-on-unmount) and #4/#8 (unconditional work even when profiling is off) are worth a maintainer's explicit sign-off before merge, and the test coverage gaps (#2, #3, #6, #13) mean the new carrier-rebind logic isn't actually exercised by CI yet. |
| * | ||
| * <p>Used by java-lang-21.0 {@code VirtualThreadInstrumentation} to swap the entire scope stack on | ||
| * mount/unmount. | ||
| * <p>With the legacy context manager, {@code swap()} rebuilds the whole scope stack on every |
There was a problem hiding this comment.
nit: the legacy context manager doesn't rebuild the whole scope stack, but it does wrap the stack and context together so the original stack can be restored when the context is swapped back.
| this.context = this.previousContext.swap(); | ||
| this.previousContext = null; | ||
| if (LEGACY_CONTEXT_MANAGER) { | ||
| AgentTracer.get().getProfilingContext().clearContext(); |
There was a problem hiding this comment.
I assume it's ok to just clear the profiling context on unmount - but just want to be sure :)
What Does This Do
The virtual-thread instrumentation swapped the whole scope stack on every VirtualThread.mount()/unmount() (i.e. on every park/unpark).
The trace scope stack lives in a virtual-thread-aware ThreadLocal, so it follows the VT across park/unpark and carrier migration on its own. It only needs seeding once (first mount), never swapping again. The ddprof profiler context is different: it's keyed by the carrier OS thread, so it's re-bound on mount and cleared on unmount, but only when carrier-bound profiling is active.
I added few JMH bench
Results (per park/unpark, JDK 21)
currentCycle_profilingOffproposedSteady_profilingOffcurrentCycle_profilingOnproposedRebindUnbind_profilingOnAnd the deltas:
Motivation
Additional Notes
Contributor Checklist
type:and (comp:orinst:) labels in addition to any other useful labelsclose,fix, or any linking keywords when referencing an issueUse
solvesinstead, and assign the PR milestone to the issue/merge. You can also:/merge --commit-message "..."/merge -c/merge -f --reason "reason"; please use this judiciously, as some checks do not run at the PR-level (note: the PR still needs to be mergeable, this will only skip the pre-merge build)Jira ticket: [PROJ-IDENT]