journeys tui-kit: the 120 fps gradient spins to each frame by default (mini pace probe)
Merged
LA
5d ago+25+25+01234567890123456789-10-10-01234567890123456789
Summary
The 120 fps gradient now spins to each frame's due time by default (--pace spin).
The pace probe ran on a fleet mini: job 0a364ec0ef40c744b2b5ec95, cmux21, feat 47f9f841aeb8, macOS 27.0, Python 3.9.6, 30 s per variant, 3600 frames due. Frame intervals in ms:
| variant | p50 | p95 | p99 | max | frames late by more than one frame | CPU |
|---|---|---|---|---|---|---|
| sleep (the old default) | 0.39 | 47.21 | 65.71 | 72.91 | 2459 | 4 % |
| hybrid (2 ms poll) | 0.48 | 42.62 | 54.58 | 54.96 | 2026 | 6 % |
| spin | 8.33 | 8.33 | 8.33 | 8.34 | 0 | 100 % |
| taskpolicy -t0 -l0 + sleep | 0.44 | 44.11 | 62.73 | 72.10 | 2287 | 4 % |
| taskpolicy -B -p + sleep | 0.42 | 45.62 | 63.52 | 72.65 | 2359 | 4 % |
| caffeinate -i + sleep | 0.42 | 43.71 | 60.94 | 71.68 | 2393 | 4 % |
| sleep again (run last) | 0.42 | 42.50 | 62.64 | 72.68 | 2365 | 4 % |
What the data shows (the symptom; the cause is not proven): the process's sleeps wake in bursts. There are about 1100 to 1300 voluntary context switches for 3600 frames, so the process wakes every 25 to 40 ms and draws the due frames back to back (p50 0.4 ms).
- The draw is not the cause (draw_ms p95 0.45).
- The process is not in the background class (darwin_bg no).
- Latency and throughput tier 0, leaving the background class, and caffeinate do not change it.
- Only polling the clock holds 120 fps. Each spinning gradient takes one core (
scenario --panes 12starts 1 to 7 of them, usually 2 to 4).
Changes:
gradient --pace spinis the default: one core per 120 fps gradient.topkeepssleepat 30 fps.- The heavy profile's hidden 10 fps gradients pass
--pace sleep, so 40 workspaces do not each take a core. - The self-test's
TUI-KIT-PACEline now reports the spin variant.
Testing
Observed / Not run
Observed:
- The table above (fleet job
0a364ec0ef40c744b2b5ec95). - On cmux-orchestrator-1,
python3 scripts/journeys/tui-kit/test_tuikit.pypasses 33 tests. The newtest_pace_defaultschecks the defaults: gradient spin, top sleep, hidden gradients sleep. scripts/cmux-next/prepush-gate.py: no new violations.
Not run:
- A cheaper fix: setting the thread's QoS class with
pthread_set_qos_class_self_npor waiting withmach_wait_until. It needs new probe variants and a mini run after merge. - The gradient's pace inside a real cmux terminal pane. The process tree there is different: the cmux daemon is the shell's parent. All the data above comes from a job runner with no GUI. In a pane, a spinning gradient shares cores with the app, the daemon and the capture, so it can still be preempted.
- Timer coalescing or the QoS class as the cause is not proven.
Changelog
none