Repository navigation
The run queue's operations cost less and decide as before - #1260
Conversation
Review of #1260 at 86e6c6a (code 8750f77)Architecture. Ownership and representation stay as they were. Each change is local and exact: a floor shift replaces a power-of-two divide, a lone entity leaves without the general path, the whole-job weight needs no divide, a sorted 1. No decision changes: verified by reading the code
2. The differential test
3. The frozen reference's lifecycleIt is recorded where it is enforced: in 4. The records
FindingsBLOCKER: none. SUGGESTION
PR description, for the orchestrator
Landing preconditions
LAND, pending a green guest run 43062760497 and an A/B meeting the three conditions above. |
The benches are down, so the A/B of its run-queue change and its Linux reference are recorded as owed against the three records it updates. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Orchestrator verification, landing under the benches-down rule (metal unavailable; review + host + guest, metal owed):
|
On both metal benches a same-CPU futex ping-pong spends about half its round trip in the run queue: every park dissolves its job's group and every wake forms it again, each through two EEVDF queues. The probes put the queues' joins, leaves and charges at about 240 ns a park and wake on both benches, and the group map's insert and remove, with the job reference the group drops, at 130 ns on the T14 and 55 on the AMD bench. Each operation now costs less, and none decides differently: - A division by a power of two is a shift. Every thread weighs 2^16, and so does a group whose job's runnable threads are all on its CPU, so most of the queue's 128-bit divisions go. - A lone entity leaves without the general path: alone it is the whole of the queue's sum, so it takes no lag and leaves the mean at its own virtual time, which is where the general path rebases to. - A group holding all of its job's runnable threads weighs the job's weight without a division. - The groups a queue holds are a vector in job order rather than an ordered map: no node is allocated, freed or walked, and every walk of the groups keeps its order. A pass already walks every group, so the vector's linear insert costs it no more. - A task carries its mask's reach, computed where the mask is loaded, and a queue counts pinned and unrestricted tasks by it rather than comparing a 128-byte mask at every entry and pick. The run queue as it stood is kept as a test-only reference, and a differential test drives both over 300 generated schedules of 2,000 operations: several jobs whose threads spread over up to three queues, so not every group weight is a power of two, real-time threads, masks of one, two and every CPU, and masks that move while their task is out. Every answer, every query of every queue and every standing a leaving task takes with it must be the same. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d the guest profile keeps its checks The wakeperf diagnosis's segment probes, one boot a bench on main 422915e, split `time.yield`, `pingpong.futex.same_cpu` and `ipc.reqrep.8` into the run queue, the pass's fixed work, the timer, the switch and the rest. The same boots measured what `[profile.toyos]`'s debug assertions and overflow checks cost, under five percent at the median row; they stay, recorded as declined. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`div` and `div_rem` are tested against `div_euclid` and `rem_euclid` at extreme and random 128-bit numerators, every power-of-two divisor and others, so the shift keeps a check of its own once the frozen reference goes. Halving the power-of-two remainder, which no schedule can observe, reds it. The reference's header says exactly what it is, an index into the group vector is `i` where `at` names a place everywhere else in the module, and the profile record names who kept the checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The benches are down, so the A/B of its run-queue change and its Linux reference are recorded as owed against the three records it updates. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
47091b4 to
817938d
Compare
Draft, LAND by review. The host checks, the models and guest run 43062760497 are green; the metal A/B is owed (below), the benches being down.
Problem
On both metal benches a same-CPU futex ping-pong spends about half its round trip in the run queue. The segment probes of the wakeperf diagnosis (evidence below) read 1,837 of 3,998 probed ns on the T14 and 1,645 of 3,336 on the AMD bench: every park dissolves its job's group and every wake forms it again, each through two EEVDF queues. The finer probes put, per park and wake:
A finer probe serialises the pipeline around each segment, so these overstate each part; their ranking is what this change acts on.
What changes
Each run-queue operation costs less, and none decides differently.
eevdf::div,eevdf::div_rem, used byQueue::rebase,issue,enter,lag_ofandcharge). Every thread weighs 2^16, and so does a group holding all its job's runnable threads, so most of the queue's 128-bit divisions, each a__divti3call on x86-64, go.Queue::exit): alone it is the whole of the queue's sum, so it takes no lag and leaves the mean at its own virtual time, which is where the general path rebases to. The single-thread group of every ping-pong takes this path at both levels.Job::group_weight).BTreeMap(RunQueue::members,member,enter,leave,refresh,ready,pop_surplus,groups): nothing is allocated, freed or walked through nodes, and every walk keeps its order. A pass already walks every group inrefresh, so the vector's linear insert costs a pass no more.affinity::Reach,CpuMask::reach,Task::reach, set whereTask::refresh_affinityandTaskBuilder::buildload the mask), and the queue counts pinned and unrestricted tasks by it (RunQueue::count_in,count_out,takers,holds_movable) instead of comparing a 128-byte mask at every entry and pick, which on x86-64 is amemcmpcall.Not changed: the parked map (
CpuSched'sBTreeMap), which is the wake's largest single item but not the run queue; a hash table there needs a seeded hasher in the core. The job reference a group takes and drops (an atomic each way) stays: the group needs its job's runnable count while its only thread is running, and nothing else holds that count for it.For the branches that rebase on this one (stopgate #1241, handles3): the functions changed are those named above, in
kernel/pure/sched/{eevdf,queue,job,task,affinity}.rs. Nothing inkernel/src/changes, andcpu.rsdoes not.Why no decision changes
kernel/pure/sched/queue/reference.rsis main's run queue and its EEVDF queue at 67713cb, test-only, less the protocol port's escape hatches, the comments and what the test does not call.queue/differential.rsdrives both over 300 generated schedules of 2,000 operations: up to four jobs whose threads spread over up to three queues, so group weights are not all powers of two, real-time threads, masks of one, two and every CPU, masks that move while their task is out, inserts with carried standing, picks, charges, yields, put-backs, blocks, surplus answers, removals and refreshes. Every answer, every query of every queue (lengths, takers, weight, groups in order, keys in order, decision instant) and every standing a leaving task takes away must be the same. The reference goes with the test when a change means to decide differently.Negative controls, each a mutation applied to this head, run and restored (log in the evidence):
div_rem's non-power-of-two remainder halveddiv_rem's power-of-two remainder halvedfine(ns)is a multiple of every power-of-two weight up to 2^48, so that remainder is always zeroChecks
The code the A/B measures is 8750f77's. 9e31612 after it adds
eevdf's division test, renames a local index inRunQueue::{enter, leave}, rewords the reference's header and the profile record: no behaviour changes, so the unit tests and clippy ran again there and the mutations and models, run at 8750f77, stand. Logs: https://github.com/ToyOSOrg/evidence/tree/3ddc686338d20b13528b597a7437e3b027479728/checks, and the later ones linked in their rows.cargo test -p kernel --lib, and with--features sched-checkcargo clippy -p kernel --lib --tests --features sched-check, and withsched-check,protocol-portand the kernel's controls;-p kernelforx86_64-unknown-none(default, andboot-actuators,test-actuators) andaarch64-unknown-none-softfloat; the adopted lints,-D warningsbuild/clippy.rsthat compile these filesdiv_and_div_rem_are_floor_division, under the mutation the differential test cannot see (the power-of-two remainder halved)cargo run -- --ci host modelskernel-simandkernel-loomdrive this run queuepolicy,scenarios,topologyanddeadline_claim_racetests, and every model's control red as required (log)std_threads_count_the_cpus_and_give_back_ended_threads,affinity_pins_hold_on_every_cpu,vector_state_survives_a_switchNo aarch64 guest row: nothing here is architecture code or ordering, and the pure core's arithmetic and containers are what the host tests drive.
Host microbenchmark, indicative only (Apple M-series under other agents' builds, the kernel's
toyosprofile,git archivetrees of main and 8750f77, runs in the order below, each figure the best of 7 repetitions; scratch code, not committed, in the evidence). Every reading, ns a park and wake:The run-queue operations read 117 to 122 on main and 70 to 96 at head in every run. The whole pass is noisier: main's three runs read 266, 912 and 261 with nothing else parked, the head's 240, 295 and 213. The host is not a target: its divider and memory system differ, and the metal A/B below is the measurement.
Owed on metal
Both metal benches are down, so metal is no longer a landing condition here. The speed-up is not yet measured on metal. Recorded as owed:
issues/a-yield-…,a-two-thread-ping-pong-…,a-small-message-round-trip-…): main must readtime.yieldandpingpong.futex.same_cpufaster by more than 3% on both, and no scheduler, IPC, wake or spawn row slower.RunQueue::{enter, leave}.Issue records
issues/a-yield-costs-…,issues/a-two-thread-ping-pong-…andissues/a-small-message-round-trip-…gain where their time goes, from the diagnosis.issues/the-guest-profile-keeps-its-checks-which-cost-under-five-percent.mdrecords the checks' measured cost and that they stay.Evidence
Branch
pr-1260, https://github.com/ToyOSOrg/evidence/tree/3ddc686338d20b13528b597a7437e3b027479728:diagnosis/holds the probes' readings, the tables and the tools behind the figures above (scout branchwakeperf-scout, pushed, never lands);checks/holds this PR's check, mutation and microbenchmark logs.Risks
refreshalready walks them all at every pick, so a pass is linear in them either way.🤖 Generated with Claude Code