Repository navigation
perf: plain-return fast path for Op::Return (ADR 0039 I1) - #440
Merged
Merged
Conversation
An instruction trace of one call-loop iteration on x86 (1385 vs CRuby's ~328 at similar IPC) shows the gap is spread over the call path, Op::Return, and ~50 instructions of per-op dispatch/fuel overhead. ADR 0033 ruled out a wholesale lean rewrite, so this plans five narrow increments that each remove bookkeeping paid when the feature it serves is unused, each gated on a measured cycles gain.
A plain method return paid ~260 instructions on x86 for checks that find nothing to do: the ensure scan, pending break and kept-block tests, a 192-byte frames.pop() move, the transfer sweep, and the $!/class-body/swap_return/aux tests. When no transfer is in flight and the frame has no aux box, class body, swap_return or dm share, Return now drops the frame in place, restores $~, rebalances the stack only if needed, and releases the locals. Every other shape takes the unchanged slow path. The o.n call loop retires 97 fewer instructions per iteration and takes 13% fewer cycles on starship (ADR 0039 I1). Closes #439.
gapscan PR diffThis PR doesn't touch any file that affects gapscan classification — See docs/gap-reports/ for the dataset and methodology. |
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 join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Closes #439. This is increment I1 of the new ADR 0039 (per-call and per-op fixed cost), which this PR also adds. Part of #378 / #385. It is independent of #438.
ADR 0039
Trace. On starship, a gdb
stepitrace of onewhile i < n; o.n; i += 1; enditeration counts 1385 instructions. CRuby runs the same iteration in ~328, at a similar IPC.Where they go.
Op::Returncheck_fuel(~50 per op, 8 ops)Scope. ADR 0033 already ruled out a wholesale lean rewrite and hot-op inlining. This ADR goes after something narrower: the bookkeeping every op and every call/return pays even when the feature it serves is not in use.
Plan. Five increments, each as its own PR, each gated on a measured cycles gain:
Frameand a merged call chainI1: what changed
Op::Returnnow tests one conjunction first. It holds when:$!state),swap_returnand nodm_share.When it holds,
Return:localsandsaved_last_matchout of the frame,truncate, rather than moving 192 bytes out withpop),$~,Every other shape falls through to the unchanged slow path.
The draft ADR planned a
Frame::plain_returnflag computed at push. It turned out not to be needed: the tested fields share the frame's cache lines. Testing them directly also means there is no flag invariant to maintain at every site that makes a frame non-plain.Numbers
starship (x86).
taskset -c 5 perf stat, 5 interleaved rounds. Base is master 701b0de; both builds useline-tables-only.o.nloop (30M iterations), instructionso.nloop, cyclesshape-bench.rbcycles, 3 roundso.no.n1(i)o.n2(i,i)Mac Studio (M2 Max). 5 interleaved rounds for call0 and Rails, 3 for shape-bench (medians). The load average was ~8–13.
o.no.n1o.n2**optsThe Rails runs were under load, so read the Rails number as a direction, not a measurement.
Verification (Mac)
STRESS_GC=1,RUBYRS_JIT_NATIVE=1, and tier 2 withTHRESHOLD=1. The newplain_return.rbalso passes under default andSTRESS_GC=1.--no-default-featurescheck (this covers theregex-off cfg).-D warnings, both the rubyrs and the workspace leg.--test embed421/421, perf/check 9/9, lint-gc-rooting, lint-doc-orientation.New fixture
plain_return.rbcovers:return$~being method-local across a calleereturnthrough anensureand out of awhile(both take the slow path)Found in passing, not fixed here
r = class Foo; 7; endgivesr == Fooin rubyrs and7in CRuby. This is already true on master. The code comment inOp::Returncalls it rubyrs's return-the-class behaviour, but it is not documented indocs/SUBSET.md.🤖 Generated with Claude Code