Skip to content

Store dictionaries as sorted vectors with inline name keys - #610

Merged
Velli20 merged 1 commit into
mainfrom
dictionary-flat-inline-names
Oct 27, 2026
Merged

Velli20 merged 1 commit into
mainfrom
dictionary-flat-inline-names

Conversation

@Velli20

@Velli20 Velli20 commented Oct 25, 2026

Copy link
Copy Markdown
Owner

Summary

  • Dictionary stores its entries in a private Vec<(PdfName, ObjectVariant)> sorted by key, instead of a public BTreeMap<Vec<u8>, ObjectVariant>. Lookups use binary search, and iteration stays in key order.
  • PdfName stores names of up to 22 bytes inline, so dictionary keys no longer allocate. Longer names use a shared Arc<[u8]>. parse_name returns a PdfName and only allocates when the name contains # escapes.
  • Dictionary::new and its sort-and-dedup pass are removed:
    • the parser inserts each entry as it reads it, so a repeated key keeps its last value, as with the old BTreeMap;
    • decryption decrypts values in place through the new iter_mut();
    • inline-image normalization builds its dictionary directly; the abbreviated key still wins.
    • Callers with loose entries, mostly tests, use the new FromIterator impl.
  • optional_entries and lenient_entries borrow the resolved sub-dictionary instead of cloning it.
  • Name values are still PdfString { bytes: Arc<[u8]> }, so the 73 read::<Arc<[u8]>> sites still only clone an Arc and allocate nothing.

Benchmarks

document_open on the shipped profile (opt-level "z", fat LTO). Medians of 5 interleaved rounds against main, run before the parser switched to inserting keys as it reads them:

Document main this PR (sort once)
Gradients.pdf 34.69 ms −3.2%
W3Schools.pdf 3.63 ms −4.6%
dd5cf1a7….pdf 74.83 ms −3.9%
RadialGradientFills.pdf 5.52 ms −2.7%
PlaygroundMDN.pdf 4.38 ms −2.4%
test6.pdf 0.27 ms −2.6%
webgl.pdf 107.65 ms −1.3%
is.pdf 292.61 ms +0.4% (noise)

Inserting each key in sorted position as the parser reads it, instead of sorting once at the end, measured 0.2–2% slower on six documents and slightly faster on two, close to the noise floor. The final code was not benchmarked against main directly.

Inline keys compared with Arc<[u8]> keys, everything else identical: −1.8% to −3.1% on four documents, within noise on the rest.

Test plan

  • cargo test --workspace --exclude pdf-graphics-skia --exclude pdf-conformance
  • cargo clippy --workspace --all-targets --exclude pdf-graphics-skia --exclude pdf-conformance -- -D warnings
  • cargo fmt --all --check, minimal-feature check
  • Skia crates and the conformance tool were not built locally (Skia binaries can't be downloaded here); CI covers them

🤖 Generated with Claude Code

Dictionary entries move from a BTreeMap<Vec<u8>, ObjectVariant> to a
private Vec<(PdfName, ObjectVariant)> sorted by key, looked up by binary
search. PdfName stores names of up to 22 bytes inline, so dictionary keys
no longer allocate, and the parser builds names straight from the input
unless they contain # escapes.

Dictionary::new and its sort-and-dedup pass are removed. The parser
inserts each entry as it reads it, so a repeated key keeps its last
value as before; decryption decrypts values in place through iter_mut;
inline-image normalization builds its dictionary directly. Callers with
loose entries use the new FromIterator impl.

optional_entries and lenient_entries borrow the resolved sub-dictionary
instead of cloning it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Velli20
Velli20 merged commit cf0a7d6 into main Oct 27, 2026
10 checks passed
@Velli20
Velli20 deleted the dictionary-flat-inline-names branch October 27, 2026 15:26
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