perf(presentation): specialize retained-pixel hashing - #1598
Draft
rlanday wants to merge 9 commits into
Draft
Conversation
This was referenced Sep 8, 2026
Merged
Owner
|
Reviewed the retained-pixel-only delta and the stated validation. Leaving this draft unmerged: the unrestricted EV pair still reports +2.369% CPU / +2.155% cycles, while the reverse pair was excluded for throttling. A rebase alone does not resolve that acceptance gap. Please rerun clean matched AB/BA EV workloads on the current baseline, preserving equal guest progress, retained detail and audio, and document the deterministic-hasher worst-case/guest-controlled-offset risk (particularly SavedPixels.detail). Once #1597 lands, drop its already-merged changes from this stack and mark ready only when those outstanding checks are resolved. |
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.
Summary
Use a small private integer hasher for four retained-presentation tables
whose keys are exclusively
usizepixel/sample offsets. SC2K's scriptednew-city phase uses 31.6–40.4% less process CPU time on the refreshed 0.39.1
stack, on top of #1597; the earlier 0.39.0 experiment measured 30.25–32.10%.
Both are after #1524. Clock/load conditions account for part of the spread,
so this is not evidence that 0.39.1 increased the optimization's effect.
Draft: EV is not cleared. The unrestricted current EV pair used 2.369%
more CPU and 2.155% more cycles despite fewer instructions. A second pair was
severely throttled and its apparent CPU win is excluded. The current and
historical results, including unfavorable measurements, are detailed below.
Stacked on #1597.
The PR targets master, so its diff includes #1597 until that parent merges.
The incremental change from
a7689d11to this headf72155ccis onlysrc/memory/presentation.rsand itscontrols.rstest helper: 94 insertions,13 deletions. No private benchmark code or bulk-fill prototype is included.
Standalone CI on
f72155ccpasses all four jobs: Linux build/tests, headless/package, licenses and the
nonblocking formatting/clippy job. The draft status remains for the EV
performance check, not a failing correctness test. Local combined tests are
documented below.
No drawing, invalidation, memory observer, guest instruction, sound callback
or validation operation is removed. No JIT/admission threshold changes.
How this fits with the other performance work
The font/retained-text overhaul, Systemless #1524, merged in 0.39.0. The effect sizes below are SC2K process CPU time for the stated replay, not GUI Activity Monitor percentages or a cross-workload ranking.
This pixel-index-hash change previously measured about 31.2% additional CPU reduction on top of #1597, also after #1524 on 0.39.0. It is not included in any of the five other PRs above. A bulk retained-fill prototype is being tested on top of that; it has no measured effect size yet. Sequential reductions apply to remaining work, not by adding percentages; there is no directly measured all-PR total.
What the text-rendering overhaul changed
#1524 added CPU-side retained high-resolution glyph coverage alongside guest framebuffer bytes. Glyph masks are cached: the new hot work is repeatedly erasing/repainting their pixels and updating subpixel, palette and snapshot state, not necessarily rasterizing font outlines again. GPU presentation does not remove this bookkeeping.
That changes what each optimization can save. #1421 avoids rebuilding themed background artwork, but replay still performs observed pixel writes and actual title glyphs are drawn separately. #1423 makes colour resolution cheaper, not retained-pixel bookkeeping. #1597 removes whole redundant paint passes, so it also avoids their new text cost. This hash change and the separate unpublished fill work target that remaining per-pixel cost directly.
We have not run a matched before/after-#1524 experiment for the older PRs. Their percentage changes therefore cannot yet be quantified; the old numbers must not be ranked against the 41%/31% post-overhaul results as if they shared a baseline. More rendering CPU can also dilute a JIT optimization's percentage without making the JIT optimization itself slower. The mechanism explains why priorities changed, but is not proof of a particular slowdown or speedup caused by #1524 alone.
Theme is another comparison boundary: the 41% and 31% post-overhaul replays use
classic-system7. #1421's non-classic theme-artwork cache is bypassed in that path, so it does not add its historical 20.8% saving to those runs. The old5f21681CLI default wassystemless-default; the current CLI default isclassic-system7. Changes in theme, scheduling and retained rendering must be separated before attributing a changed effect size specifically to #1524.Why these tables are hot
High-resolution fonts retain additional sample coverage without changing
the guest's logical pixels or text metrics. Painting a character, clearing
its old background, saving/restoring a window or menu strip, and recoloring
a palette all consult this retained data. A same-value logical framebuffer
write can erase high-resolution glyph detail; simply skipping those writes
would not preserve behavior.
After PR1597 stopped painting chrome during individual sound slices, outer
window/menu composition remained a major SC2K cost. A diagnostic profile
put 1,773 of 2,277 main-thread samples in outer chrome. The three exclusive
hash_one, hash-map removal and SipHash-write buckets totalled 769 samples.That profile identified an opportunity; it did not predict an exact saving.
Every lookup previously paid the default general-purpose hashing cost for
a machine-word numeric offset. The retained index is already available;
there is no arbitrary string to hash. This patch specializes that operation
without changing the collection's normal key equality, collision handling,
allocation, growth or removal semantics.
Implementation
SavedPixels.detailDetailCell.inkPresentation.inkPresentation.run_inkThe private
PixelIndexMap<T>andPixelIndexSetaliases still use thestandard
HashMap/HashSet, withBuildHasherDefault<PixelIndexHasher>.For a single
usize, the hasher multiplies the full word by an odd 64-bitconstant, then XOR-folds its upper half into its lower half. It supplies
mixed bucket/tag bits with a short sequence of integer operations. It does
not truncate a 64-bit key to 32 bits and uses ordinary portable Rust with
wrapping arithmetic. There is no architecture-specific assembly or unsafe
code. A byte-write implementation fulfills the hasher interface, but the
aliases only admit
usizekeys.The mixing technique follows the existing write-journal
AddressHasher;this patch does not change that journal or share new mutable hash state.
The two private implementations differ in their typed input (
usizeversusu32). Keeping this first experiment scoped to retained-presentation tablesavoids also changing the bus/journal types and their generated code. Sharing
one private helper is a reasonable follow-up/cleanup option, but has not been
measured and should not silently be substituted for the benchmarked patch.
The tuple-keyed offscreen text-run set and ordered offscreen map are left
unchanged. There is no new dependency, environment switch or public API.
Correctness and security boundaries
Hash iteration order changes. Palette recoloring writes disjoint pixel
ranges, snapshot remapping preserves each key, and the production
transform_detailcallbacks are pure per-offset/value transformations.None relies on the former randomized traversal order.
This is deliberately not a global replacement for randomized hashing.
Guest drawing can influence pixel positions: these are constrained numeric
offsets, not cryptographically trusted input. The new deterministic hasher
does not retain the default hasher's attack resistance against chosen
keys. Existing index bounds, collection equality/collision handling and
memory safety remain; hostile-key performance is a review consideration,
not something the game benchmarks prove safe in all circumstances. In
particular,
Presentation.ink/run_inkare surface-bounded and eachDetailCell.inkis bounded by scale², butSavedPixels.detailcan coveroffscreen/snapshot ranges too. A finite capacity bound is not by itself a
proof against expensive probing or quadratic total work from chosen keys.
The default hasher's stored state also disappears from these internal
types, changing structure layout. We have not isolated that contribution
with a padded-layout ablation. The measured win belongs to the complete
patch, not to a claim that hashing arithmetic alone explains every cycle.
Refreshed 0.39.1 measurements and remaining EV check
Both arms use upstream
b5f4f083, current #1597 and identical #1596/privatephase-counter overlays, default release settings and the classic theme. Only
the two retained-presentation hash source files differ. This is separate from
the bulk-fill prototype, #1421/#1423 and m68k #174.
The scripted phase clocks, guest work, archive/input bytes, start/end logical
PNGs and captured mono audio match exactly in every pair. No compiler, sampler,
GUI or other emulator overlapped. Before/after process lists record remaining
macOS indexing/media/UI activity; this was not a completely quiescent machine.
Both runs show a substantial CPU saving, consistent with the earlier 0.39.0
experiment. Speed-limit snapshots were 100 throughout, but cycles/CPU-ns were
3.6074→4.0204 and3.8133→3.9726; clock/load differences contribute to the wider
CPU spread. The raw paired CPU median is −36.007%. Do not interpret this as
proof that 0.39.1 increased the patch's benefit relative to the earlier31%.
In the second EV pair, candidate speed limits moved 100→60 and baseline 60→54.
The apparent CPU improvement is confounded and excluded; the raw two-pair
median must not be quoted as an EV benefit. The unrestricted pair is a small
CPU/cycle regression signal, not something to dismiss because instructions
fell. A clean rerun is needed; a layout effect is a hypothesis, not a diagnosed
cause. This unresolved EV result is why this PR is draft.
Completed matched CPU measurements (0.39.0, after #1524)
Intel macOS, Rust1.96.1, default production release: fat LTO, one codegen
unit, identical published dependencies and private benchmark lock. The
private replay overlay is identical between arms and is not for publication.
The baseline already contains PR1597. Runs alternate AB then BA, serially,
with no compiler, sampler, GUI or second emulator overlapping acceptance.
SC2K: frontend ticks1800→2400, exactly600simulated ticks and31,686,472guest
instructions per measured phase. CPU is process user+system time:
Median paired CPU change -31.177%. All before/after CPU speed limits
were 100%, but those snapshots do not prove constant clocks during a run.
Cycles per CPU-ns were3.7260→3.9040 and3.8379→3.9605: clock differences can
affect the size of the CPU delta. Both pairs reduce CPU time and cycles.
EV Override flight: ticks3300→6300, exactly3000ticks and202,572,307guest
instructions per phase:
No established EV CPU win; a small regression is not excluded. Pair1
limits were 100→97(base),97→100(candidate); pair2all100. The small positive
cycle result must not be silently dismissed as thermal noise. This EV
flight scene suppresses native Mac window/menu chrome and draws its own
HUD/playfield, so it has substantially less exposure to the targeted work.
That explanation does not apply to every EV dialog or scene.
All four runs per app match archive/input bytes, phase entry/exit guest
clocks/work, logical checkpoint PNGs and captured mono samples/hash exactly.
The emulator performs the same guest work; we do not normalize divergent
runs. These are not GUI Activity Monitor percentages, FPS, every transient
frame, high-resolution game-capture comparisons or stereo-device tests.
In particular, the previously reported spinning-newspaper >100%CPU peak
has not been measured by this city replay.
Validation
growth, replacement, entry updates, membership, removal/missing removal
and clear against standard collections.
3 ignored on 0.39.1 + current#1597 + this hash patch + the local bulk-fill
prototype (
906f7486). This is not relabeled as standalone hash-PR CI.Desktop on that same combined tree also completed: 88 passed, 0 failed, 1 ignored,
with the independently reproduced upstream Metal minification test explicitly
excluded (not passed). The earlier standalone attempt
was interrupted by reboot, not passed. Standalone CI now passes; the clean EV regression check remains pending.
overlap, indexed/direct-color, control-corner and callback equivalence
tests are required; logical PNG equality alone is insufficient.
live level1 (2,400ticks), 3in3 prologue (1,800) and Twilight introduction
(2,400). Archive/input bytes, guest clocks/work, captured mono and final logical
PNGs match exactly. No timings from these correctness-only runs are scored.
Twilight executes 264,750 guest instructions on each current arm versus 264,783
on the old 0.39.0 stack; this is not a cross-upstream work-identity claim.
illustrated intro also match saved PR1597 archive/input bytes, exact
guest progress, captured mono and final logical PNGs. These are not full
puzzle playthroughs. Their timings are excluded because compilation
overlapped these correctness-only replays.
removal symbols no longer appearing in sampled stacks. Outer chrome
remains1,004/1,307main-thread samples. Inlining changes attribution; this
does not mean all hashing cost vanished. No sampled-run CPU is scored.
Alternatives and next steps
This experiment has one measured runtime candidate. Do not relabel ideas
below as benchmarked failures:
risks; scope is deliberately the four numeric-offset collections.
erases. That shortcut is not implemented.
need memory/sparsity and lifecycle measurements; not prototyped here.
app would work. It needs authoritative guest drawing, palette, window,
menu, theme and retained-detail invalidation. This patch only makes the
retained bookkeeping cheaper; it is compatible in principle with a later
correct repaint-coalescing design, but their combined gain is unmeasured.
bounds is another preserved follow-up lead, not part of this patch.
Reproduction and provenance
Refreshed 0.39.1 private control:
86aad255, savedsystemless-pr-sound-chrome-0391,SHA256
4d9bbcebb95ddb405eff5bdaeca6ed21a71043e40b96409b0791703f3059a59a.Refreshed private candidate:
6d3e580b, savedsystemless-pr-pixel-index-hash-0391,SHA256
5b2a295456726353cb6b7527e3109fca81c7a6d8048f704a2d55ad18dc57dc9e.Identical private lock SHA256
ce727ab3e7fa78a2f135c431211fd759025374a7062ff123c8428056b1ad265f.Reports:
logs/pixel-index-hash-0391-{sc2k,ev}-ab.txt; host-process and thermalsnapshots accompany every raw run. Full details and the excluded EV CPU pair
are recorded in
PIXEL-INDEX-HASH-EXPERIMENT-2026-09-08.md.Earlier0.39.0 provenance:
Private baseline:
271d9410, savedsystemless-pr-sound-chrome-v1, SHA256614b0d26d789c9c826ef036775ce8b4c60bc888e684b79b020ab093d865f1c17.Private candidate:
095424ce, savedsystemless-pr-pixel-index-hash-v1, SHA25652a104ca33a8960d076b121b545fe2e3f513426c93b3fa7254cff333eb23066d.Private lock SHA256
070e3942145e8ff5b097f49b60d59624587bb231f0b2af7c9fe1dd3250adc3b2.Full commands, caveats and artifact paths:
.work/steady/PIXEL-INDEX-HASH-EXPERIMENT-2026-09-08.md;reports
logs/pixel-index-hash-{sc2k,ev}-ab.txt.