Skip to content

perf(text): resolve each font slot once per run - #235

Merged
eliahilse merged 1 commit into
mainfrom
perf/text-chain-memo
Aug 26, 2026
Merged

perf(text): resolve each font slot once per run#235
eliahilse merged 1 commit into
mainfrom
perf/text-chain-memo

Conversation

@eliahilse

@eliahilse eliahilse commented Aug 23, 2026

Copy link
Copy Markdown
Member

TL;DR:

Summary:

  • measurement resolved and revalidated a run's font fallback chain for every character; w:rFonts fixes the family per slot and bold/italic only vary by whether the slot is complex-script, so (family, bold, italic) is a function of the slot within a run
  • resolve the four slots at most once each instead, with no cache, cap, key or normalisation involved; a chain too long to be worth keeping is rebuilt per character exactly as before, so peak memory is never above the base either
  • adds the slot-routing coverage the suite had none of: every character class must measure through its own slot's family, w:hint="eastAsia" must move only the ambiguous ones, and both hold when the chain is rebuilt per character rather than kept

Test plan:

  • cargo test --workspace green (85 suites, 0 failures)
  • cargo clippy --workspace --all-targets --all-features -- -D warnings and cargo fmt --all -- --check clean
  • measurement output byte-identical to the base across 45,496 differential cases (132 MB of serialized output) covering per-slot families that differ from each other, w:hint="eastAsia", complex-script style overrides, combining marks, list markers, small caps, bidi and every error path
  • benchmarked against the base, interleaved and in release, minimum of ten rounds: no case slower, from 1.00x on a one-character-per-slot run to 0.32x on a long run
  • every new assertion verified to fail against a deliberately broken slot mapping and against both ways the rebuilt-chain buffer could leak between slots

@openooxml-bot

openooxml-bot Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA — thank you! ✍️ ✅

Posted by the CLA bot.

@greptile-apps

greptile-apps Bot commented Aug 23, 2026

Copy link
Copy Markdown

Greptile Summary

This PR introduces a per-measurement font-chain memo keyed by normalized family and style, then routes paragraph, marker, tab, field, and text-run lookup through it.

  • Caches resolved chains and validation verdicts for one measure_paragraph invocation.
  • Bounds the memo at 128 entries and recomputes beyond that limit.
  • Adds tests covering key separation, cached errors and verdicts, store isolation, and MeasureInput thread-safety.
  • Updates the text crate changeset and affected measurement call signatures.

Confidence Score: 4/5

The PR appears safe to merge, with only non-blocking cleanup needed for overly verbose comments and docstrings.

The memo uses the same key normalization and lookup semantics as the existing implementation, remains scoped to one immutable store/input pair, and preserves validation and error behavior across the changed measurement paths.

Files Needing Attention: crates/ooxml-text/src/measure/chain_memo.rs, crates/ooxml-text/src/measure/mod.rs

Important Files Changed

Filename Overview
crates/ooxml-text/src/measure/chain_memo.rs Adds bounded, per-call chain and validation memoization with focused behavior tests; explanatory documentation exceeds the repository’s concise-comment convention.
crates/ooxml-text/src/measure/mod.rs Creates one memo per paragraph measurement and passes it consistently through empty-paragraph, marker, and run preparation paths.
crates/ooxml-text/src/measure/prepare.rs Replaces direct font-chain resolution and validation with behavior-equivalent memo calls across text, tab, field, and fallback-metrics paths.
crates/ooxml-text/src/measure/list_marker.rs Reuses the per-call memo for list-marker font-chain validation without changing marker measurement semantics.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
    M[measure_paragraph] --> C[Create per-call ChainMemo]
    C --> K[Normalize family and style key]
    K --> H{Memo hit?}
    H -->|Yes| R[Reuse resolved chain]
    H -->|No| I[Resolve from MeasureInput fontChains]
    I --> B{Below 128 entries?}
    B -->|Yes| S[Store chain result]
    B -->|No| R
    S --> R
    R --> V{Validation verdict cached?}
    V -->|Yes| U[Reuse verdict]
    V -->|No| F[Validate IDs against FontStore]
    F --> U
    U --> P[Prepare marker and runs]
Loading

Reviews (1): Last reviewed commit: "perf(text): memoize font chain resolutio..." | Re-trigger Greptile

Comment on lines +1 to +7
//! Per-call memoization of font-chain resolution.
//!
//! A [`ChainMemo`] is created once per [`super::measure_paragraph`] call and
//! never escapes it: one store, one immutable `fontChains` map, one lifetime.
//! A cached chain or validation verdict therefore can never outlive the state
//! it was resolved against.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Trim redundant memo documentation

The module documentation and related inline comments repeatedly explain lifetime and bounded-cache behavior already expressed by the implementation, increasing maintenance and documentation-drift costs. Keep the module summary concise and remove similarly redundant explanations at the memo declaration and integration point.

Context Used: CLAUDE.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Measurement resolved and revalidated a run's font fallback chain for every
character, even though `w:rFonts` fixes the family and style per slot: the
family is a function of the slot, and bold/italic only vary by whether the
slot is complex-script. Resolve the four slots at most once each instead.

Measured against the parent (release, minimum of six interleaved rounds):
a single-family body paragraph 110.3µs -> 65.2µs, a 40-run rich paragraph
440.0µs -> 139.7µs, mixed Latin/CJK/Arabic 176.9µs -> 107.3µs, and a
4096-character run 9.69ms -> 4.59ms.

Adds slot-routing coverage, which the suite had none of: every character
class must measure through its own slot's family, and `w:hint="eastAsia"`
must move only the ambiguous ones.

Co-Authored-By: codex <codex@openai.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@eliahilse
eliahilse force-pushed the perf/text-chain-memo branch from 34753b3 to 41b2499 Compare August 26, 2026 03:43
@eliahilse eliahilse changed the title perf(text): memoize font chain resolution perf(text): resolve each font slot once per run Aug 26, 2026
@eliahilse
eliahilse merged commit b59ba43 into main Aug 26, 2026
6 checks passed
@eliahilse
eliahilse deleted the perf/text-chain-memo branch August 26, 2026 13:27
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