perf: Add a guarded synchronous JSON-LD fast path to RdfParser (~20-25x on generated-document parsing)#1
Draft
jeswr wants to merge 1 commit into
Draft
perf: Add a guarded synchronous JSON-LD fast path to RdfParser (~20-25x on generated-document parsing)#1jeswr wants to merge 1 commit into
jeswr wants to merge 1 commit into
Conversation
Loading a Components.js application parses hundreds of small JSON-LD documents (component definitions and config files) that share a handful of well-known, prefetched contexts and a rigidly regular, generator-produced shape. The generic streaming pipeline (rdf-parse -> jsonld-streaming-parser) pays per-document parser construction and per-value async handler dispatch for JSON-LD flexibility these documents never use; profiling a Community Solid Server cold start attributed the bulk of the boot to this parsing path. This adds lib/rdf/JsonLdFastPath.ts: a specialized, synchronous JSON-LD-to-quads converter behind a conservative shape guard: - RdfParser.parse buffers candidate JSON-LD documents (path/content-type driven, capped at 8 MiB) and attempts the fast path; any document or context feature outside the implemented subset falls back to the unchanged generic pipeline, so behaviour (including error behaviour) is preserved by construction. - Each distinct root @context (URL set) is normalized ONCE with the real jsonld-context-parser ContextParser + PrefetchedDocumentLoader and cached (weakly, per prefetched-contexts record); the analysis marks terms whose definitions the converter cannot honour (scoped contexts, @reverse, non-@list containers, keyword aliases, ...) so any document using them falls back. - Term-to-IRI expansion inside the converter delegates to memoized jsonld-context-parser expandTerm calls with the same expand options as the streaming parser, so IRI semantics are identical by construction. - Quads still flow through RdfStreamIncluder, so imports and IRI validation behave exactly as before, and imported documents re-enter the fast path individually. Correctness: on the full Community Solid Server boot corpus (828 generated component documents + 220 hand-written config files), the fast path output is rdf-isomorphic to the generic pipeline for all 1048 files; all 828 component files take the fast path, and all 220 config files (which use @graph) fall back as designed. 119 new unit tests compare fast-path output against the generic parser (isomorphism or identical errors) across the emit and fallback matrix; the suite retains 100% coverage. Performance (pss-solid-test, 2-core shared box, warm in-memory corpus, medians of 7 alternating reps): parsing the 828-document boot corpus through RdfParser drops from ~3.9-5.4 s to ~0.17-0.24 s (~20-25x) with identical 34,926-quad output. Hand-written configs are unaffected (they keep the generic path). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Jul 5, 2026
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
Adds a guarded, synchronous JSON-LD fast path to
RdfParserfor the fixed-shape, known-context documents that Components.js itself generates and consumes. Any document or context feature outside the implemented subset transparently falls back to the unchanged generic pipeline (rdf-parse→jsonld-streaming-parser), so behaviour — including error behaviour — is preserved by construction.Loading a large Components.js application (e.g. Community Solid Server) parses ~1000 small JSON-LD documents that share a handful of prefetched contexts and a rigidly regular, generator-produced shape. Profiling a CSS cold start showed the bulk of
ComponentsManagerloading time inside the generic JSON-LD machinery (per-document parser construction, per-value async handler dispatch, context re-processing). This PR removes that cost for the generated documents while leaving every other document on the spec-compliant path.Design
RdfParser.parsebuffers candidate JSON-LD documents (by path extension / content type, capped at 8 MiB — larger documents stream through the generic parser) and attemptstryParseJsonLdFastPath; onundefinedit replays the buffered text through the generic pipeline.@context(URL set) is normalized once with the realjsonld-context-parserContextParser+PrefetchedDocumentLoader, then analyzed and cached (in aWeakMapkeyed on the prefetched-contexts record, so caches are per-ComponentsManagerand leak-free). The analysis marks unsafe terms — scoped contexts,@reverse, non-@list/@setcontainers, keyword aliases,@vocab-typed coercions, nulled terms, … — and any document using one of them (as a key or@typevalue) falls back.jsonld-context-parserexpandTermcalls with the same expand options as the streaming parser, so IRI semantics are identical by construction. Canonical numeric lexical forms andrdf:JSONcanonicalization mirror the streaming parser exactly (samecanonicalizepackage).RdfStreamIncluder: imports and IRI-validation warnings behave exactly as before, and imported documents re-enter the fast path individually.@graph,@reverse,@language,@index,@nest,@base,@vocab, nested@contexts, …; the converter additionally bails (→ fallback) on relative IRIs, unknown terms, blank-node predicates, list-of-lists, non-canonicalizable numbers, and every other observed divergence. Two subtle streaming-parser behaviours were mirrored deliberately rather than "fixed": key-order-sensitive@jsonvalue objects, and non-canonicalized explicitly-typed numeric@values (both bail to the generic parser except in the exact shapes it handles as JSON).disableJsonLdFastPath: trueinRdfParserOptionsforces the generic pipeline.Correctness evidence
oracle.js, available on request): all 1048 files of a real Community Solid Server 7.1.9 boot — 828 generated component documents + 220 hand-written config files — parse rdf-isomorphic to the generic pipeline (identical error behaviour for error cases). All 828 component files take the fast path; all 220 config files (they use@graph) fall back as designed.@json, numbers/booleans/null, blank nodes, imports, buffering/cap/replay) and the fallback matrix (every unsafe-term class, every guard rejection)..jsonldfixtures now go through it.Performance
Micro-benchmark (
microbench.js): parse the 828-document CSS boot corpus (in-memory text, warm) throughRdfParser, medians of 7 alternating fast/stock reps, identical 34,926-quad output. Box: pss-solid-test (2-core shared,nice -n 18; absolute numbers are noisy on this box — the ratio is the claim, arms interleaved in the same run):End-to-end CSS cold start (the real target): the same fast path back-ported onto the componentsjs 5.5.1 that Community Solid Server 7.1.9 actually boots (validated first: all 1048 boot files rdf-isomorphic patched-vs-stock on the 5.5.1 stack), measured as
node bin/server.js -c config/file.jsontoListening to server, alternating arms, fresh data root per run:−6.3 s, a 1.59× faster cold start from this change alone (833 documents fast-pathed, 145 hand-written configs fell back as designed). The stock median reproduces the ~17.5 s baseline from the earlier profiling. The remaining config-parse cost is per-file context re-normalization on the fallback path — that is what LinkedSoftwareDependencies#170 (shared context parser + normalized-context cache) addresses; the two changes compose.
Compatibility notes
canonicalize(~1 kB, already a transitive dependency viajsonld-streaming-parser) — used to byte-match the generic parser'srdf:JSONliterals.cjsfp-<doc>-<n>namespace, so they can never collide with streaming-parser labels in mixed fast/fallback loads (graphs are isomorphic, labels differ — same situation as any two parser instances).RdfParserOptionsflag (disableJsonLdFastPath).Generated by an agent (Claude Fable 5) on @jeswr's behalf — staged on the fork for his review before upstreaming.
Review timing: This draft was prepared with Claude; I (@jeswr) will personally review it before it progresses. I'm currently batching a lot of work in flight, so expect active review Wednesday–Friday (8–10 July).