Skip to content

perf: FaceId-keyed FontManager caches -> FxHashMap (issue #459 item 1) - #460

Merged
fredclausen merged 1 commit into
mainfrom
task-121/faceid-fxhash
Jul 27, 2026
Merged

perf: FaceId-keyed FontManager caches -> FxHashMap (issue #459 item 1)#460
fredclausen merged 1 commit into
mainfrom
task-121/faceid-fxhash

Conversation

@fredclausen

@fredclausen fredclausen commented Jul 27, 2026

Copy link
Copy Markdown
Member

Summary

Switches three FontManager caches keyed purely or partly by FaceId from Rust's default SipHash-based HashMap to FxHashMap, matching the pattern already established by atlas.rs's glyph cache. This is follow-up item 1 from #459's real-workload profiling investigation.

  • fallback_metrics_cache: RefCell<HashMap<FaceId, Option<FallbackCellMetrics>>> -> RefCell<FxHashMap<...>>
  • face_cache: RefCell<HashMap<FaceId, Option<CachedFace>>> -> RefCell<FxHashMap<...>>
  • plan_cache: RefCell<HashMap<PlanKey, Arc<rustybuzz::ShapePlan>>> -> RefCell<FxHashMap<...>> (PlanKey = (FaceId, bool, rustybuzz::Script, rustybuzz::Direction))

Why this is safe

FaceId is a small Copy enum (bare discriminants plus one System(usize) variant assigned locally during font discovery) — never derived from untrusted input. PlanKey's other components (bool, Script, Direction) are technically derived from terminal-run text via guess_segment_properties(), so they are input-influenced, but their realizable domain is a small, fixed, enumerable set (ISO-15924 script tags + a 5-variant direction enum), so a HashDoS concern doesn't apply in practice.

Deliberately left unchanged: glyph_cache ((char, GlyphStyle) key) and system_fallback_cache (char key) — both keyed by char, which comes directly from PTY output (untrusted input). Switching those raises a HashDoS-resistance trade-off that hasn't been explicitly decided and is out of scope here. (Note: atlas.rs's procedural_entries map is already char-keyed FxHashMap, so the codebase's posture on this isn't actually uniform today — this PR doesn't attempt to reconcile that, only avoids introducing a new case beyond what already exists.)

Validation

  • cargo test --all, cargo clippy --all-targets --all-features -- -D warnings, cargo machete — all green.
  • shaping_ligatures benchmark (before/after via --save-baseline): no statistically significant change detected. Expected — that benchmark's call frequency to face_cache/plan_cache per iteration is much lower than the sustained multi-row-per-frame churn that originally surfaced this cost in real-workload profiling.
  • Real-workload before/after flamegraph (btop full-screen redraw, same perf --call-graph dwarf,65528 methodology as Investigation: real-workload CPU profiling findings (idle + active TUI) — follow-up to #405, pivot from #457 #459): sip::Hasher::write and hash_one::<&FaceId> — two of the hottest leaves in the original capture at ~9.00% + ~2.92% self-time — are absent from the fixed capture with no percent-limit filter; only a 0.04% residual attributable to the deliberately-untouched glyph_cache remains. Sample counts are low (~250-400/capture) and the two captures are different windows of btop's own dynamic content, so a precise percentage delta isn't defensible, but the categorical disappearance of a top-2 hot symbol is a credible confirmation.

Adversarially reviewed for completeness (no missed construction sites or ad-hoc maps), correctness of the untouched exclusions, API/iteration-order safety, and style consistency against atlas.rs — no blocking findings.

Closes item 1 of #459.

Summary by CodeRabbit

  • Performance
    • Improved internal font caching performance.
    • No visible changes to the user interface or functionality.

Switches fallback_metrics_cache, face_cache, and plan_cache from the
default SipHash-based HashMap to FxHashMap, matching the pattern
already used by atlas.rs's glyph cache. FaceId is a small Copy enum
(bare discriminants plus one System(usize) variant); none of the
three maps' keys are influenced by untrusted input, so this is a
safe, mechanical hasher swap.

Deliberately left unchanged: glyph_cache and system_fallback_cache,
both keyed (in whole or part) by char, which is derived from PTY
output. Note the existing atlas.rs procedural_entries map is already
char-keyed FxHashMap, so the codebase's HashDoS posture on char keys
isn't actually uniform today -- this change doesn't attempt to
reconcile that, it only avoids introducing a *new* untrusted-input
case beyond what already exists.

plan_cache's PlanKey also carries Script/Direction derived from
guess_segment_properties() over terminal-run text -- these are
input-influenced, not free of it, but their realizable domain is a
small, fixed, enumerable set of ISO-15924 script tags plus a 5-variant
direction enum, so the conclusion (safe for FxHash) holds even though
the input-derivation itself is real.

Validation:
- freminal-buffer/benches shaping_ligatures (before/after
  --save-baseline): no statistically significant change -- expected,
  its per-iteration call frequency to face_cache/plan_cache is much
  lower than the sustained multi-row churn that surfaced this cost.
- Real-workload before/after flamegraph (btop full-screen redraw,
  same perf methodology as issue #459): sip::Hasher::write and
  hash_one::<&FaceId>, ~9.00% + ~2.92% self-time in the original
  capture (two of the hottest leaves), are absent from the fixed
  capture with no percent-limit filter; only a 0.04% residual
  attributable to the deliberately-untouched glyph_cache remains.

See #459 for the full profiling investigation this follows up on.
@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: caac764a-331f-4a20-a245-cfbc0cf9a2d6

📥 Commits

Reviewing files that changed from the base of the PR and between 76dd67f and f4cd974.

📒 Files selected for processing (1)
  • freminal/src/gui/font_manager.rs

📝 Walkthrough

Walkthrough

FontManager’s internal fallback metrics, face, and shaping plan caches now use FxHashMap, with corresponding default-based initialization.

Changes

Font cache map optimization

Layer / File(s) Summary
Migrate FontManager caches
freminal/src/gui/font_manager.rs
Internal font caches use FxHashMap, and FontManager::new initializes them with FxHashMap::default().

Estimated code review effort: 1 (Trivial) | ~5 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: replacing FaceId-keyed FontManager caches with FxHashMap.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch task-121/faceid-fxhash

Comment @coderabbitai help to get the list of available commands.

@fredclausen
fredclausen merged commit d1aeded into main Jul 27, 2026
19 checks passed
@codecov

codecov Bot commented Jul 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

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.

1 participant