Skip to content

perf(fonts): resolve the override directory once, not per glyph - #628

Merged
benletchford merged 1 commit into
benletchford:masterfrom
rlanday:perf/font-override-resolve-once
Aug 15, 2026
Merged

perf(fonts): resolve the override directory once, not per glyph#628
benletchford merged 1 commit into
benletchford:masterfrom
rlanday:perf/font-override-resolve-once

Conversation

@rlanday

@rlanday rlanday commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

get_font_face sits on the per-glyph drawing path, and every call resolved the SYSTEMLESS_ORIGINAL_FONTS_DIR override directory by calling env::var_os — which walks the process environment and allocates — and then took the override-cache mutex, several times per character drawn.

This PR resolves the directory on the first lookup and reuses the result. When no overrides exist — the usual case, since the variable is an opt-in hook for locally generated font blobs — subsequent lookups touch neither the environment nor the mutex (two relaxed-ordering atomic loads). A new refresh_font_overrides() re-arms resolution for callers that change the variable at runtime.

The contract change

This changes a documented behavior, and whether that is acceptable is your call. The cache previously followed the environment on every lookup, so a harness or embedder could set SYSTEMLESS_ORIGINAL_FONTS_DIR after other code had already queried font metrics and the next lookup would pick it up. With this PR, such callers must call refresh_font_overrides() after setting or clearing the variable; setting it before the first font access (the README's launch-time framing) behaves as before. We searched for affected consumers: the only user of the public fonts API we can find is this repository's own font_specimen binary, which does not use the override variable, and a GitHub code search finds no external consumers. Alternatives if you prefer them: keep the per-call environment read and only skip the mutex when no overrides are loaded — we measured that variant and it recovers approximately nothing (below) — or gate the caching behind a feature.

What we benchmarked

EV Override 1.0.1 driven headless by a scripted input replay keyed to retired instruction count: every run performs exactly the same 400,000,000 guest instructions (boot, shareware dialog, menus, pilot creation, gameplay) from a pristine save directory. Metric: child-process CPU seconds on that fixed work; arms are distinct binaries alternated within each repetition, compared as within-pair ratios so machine-load drift cancels. 16 pairs:

resolve-once vs base: −5.9% CPU (95% CI ±2.5), t = −4.54, faster in 14 of 16 pairs.

Two narrower variants measured on the same instrument were flat, which is what isolates the environment walk as the cost: hoisting the per-glyph face resolution to once per draw_char measured +0.0% (t = +0.01), and skipping only the mutex while keeping the per-call env::var_os measured +0.1% (t = +0.03).

Scope: one application, one scripted route, one host. The workload includes the menu and dialog phases where text rendering is dense; in pure gameplay scenes the per-glyph path is much cooler, so the benefit is concentrated in text-heavy screens. The claim is CPU draw on fixed work, not frame rate.

Verification

rustfmt clean, clippy --all-targets -D warnings clean, full library test suite passing on this head. The override lookup semantics are exercised by the existing font tests; behavior with the variable set at launch is unchanged.

get_font_face runs on the per-glyph drawing path and every call consulted
SYSTEMLESS_ORIGINAL_FONTS_DIR, which walks the environment and allocates,
then took a mutex. A CPU profile of EV Override attributed several percent
of the process to that, across the handful of lookups each character makes.

Resolve on first use and reuse; when no overrides exist -- the usual case,
since the variable is an opt-in debugging hook -- lookups now take neither
the environment nor the lock. -5.88% process CPU (t=-4.54, faster in 14 of
16 paired runs on a fixed 400M-instruction workload).

This changes a documented behaviour: the cache previously followed the
environment on every lookup. refresh_font_overrides() restores that for
embedders that set the variable after other code has queried metrics.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M3pHH4jDhVuFn8ZeWwkKmA
@benletchford
benletchford merged commit 4158bd8 into benletchford:master Aug 15, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants