feat: context cache - #70
Conversation
Pull Request Test Coverage Report for Build 6666516387
💛 - Coveralls |
|
This looks very neat, thanks @jeswr! Before I do an in-depth review, I have some general questions.
Pinging @sdevalk, as this will interest him regarding rubensworks/rdf-dereference.js#48 |
In the worst case it seems to be an extra 50% overhead. It can be worked around by trying to work by-reference rather than by hash most of the time (see https://github.com/inrupt/solid-client-vc-js/blob/0f8ce276b6ea8a977b9d2ea189bc92385ef44b48/src/parser/jsonld.ts#L70-L111) however; the danger here is that you accidentally consume a large amount of memory by using contexts as keys so I'm leaving this for a subsequent piece of work once better benchmarking is in place and we have a good way of pruning them quickly. FYI the custom caching mechanism in https://github.com/inrupt/solid-client-vc-js/blob/0f8ce276b6ea8a977b9d2ea189bc92385ef44b48/src/parser/jsonld.ts reduced the time on e2e tests from 20min to 20seconds.
I've disabled caching by default in b48cc07. So we will need to make an update to
I'm not seeing |
I suspect that caching by reference would consume less memory than hashing, as caching is only done on pointers towards shared memory.
Ooh, nice!
This will have to be tested by manually plugging this into jsonld-streaming-parser. |
My main concern here is that if we have a poor configuration of the |
I would suspect |
|
Superseded by #85, which refreshes this context-cache onto current master with two improvements: (1) a correctness fix — the original key collapsed contexts differing only in |
Parsing a JSON-LD context is dominated by normalization (term expansion, IRI validation, container hashing, keyword-redefinition checks). When the same context is parsed many times this work is fully repeated every time. Profiling a Community Solid Server boot showed over half of all active CPU inside jsonld-context-parser, with the Components.js ^5.0.0 context alone re-normalized ~979 times (once per referencing document, each with a distinct per-document base IRI). This adds an optional `contextCache` on the ContextParser that memoizes the normalized result of `parse`. A single cache instance can be shared across multiple ContextParser instances so a context is normalized once and reused everywhere. Caching is disabled by default; passing no `contextCache` preserves the previous behaviour exactly. - `IContextCache` interface (hash/get/set) and a bounded LRU-backed `ContextCache` implementation, keyed on the full content of the context, its parent context, and the remaining options. - Top-level object contexts are memoized directly (their key legitimately captures the base IRI). Loaded external (URL/array) contexts are instead memoized at a base-independent boundary (`parseExternalCached`): their normalization is computed once with the per-document base replaced by a fixed, unguessable sentinel, and the real base is substituted back on each retrieval by a copy-on-write walk (`rebaseSentinel`/`replaceSentinel`) that shares base-independent subtrees, so a cached entry is never mutated. The per-call base is re-applied on a shallow clone (`applyBaseEntryCloned`). - Correctness is guaranteed: the real (base-carrying) parse is always authoritative, and a sentinel result is cached only after it is verified once, on the miss, to re-base byte-for-byte to the real parse (via `Util.deepEqual`). A context whose normalization genuinely depends on the base (relative @vocab, @type-to-@base expansion) simply fails that check and is not shared -- output stays byte-identical for every context. On the CSS boot workload the external-context hit rate goes from 0% to ~99.8% and the parse is ~1.75x faster end-to-end, with identical output. Supersedes rubensworks#70. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Parsing a JSON-LD context is dominated by normalization (term expansion, IRI validation, container hashing, keyword-redefinition checks). When the same context is parsed many times this work is fully repeated every time. Profiling a Community Solid Server boot showed over half of all active CPU inside jsonld-context-parser, with the Components.js ^5.0.0 context alone re-normalized ~979 times (once per referencing document, each with a distinct per-document base IRI). This adds an optional `contextCache` on the ContextParser that memoizes the normalized result of `parse`. A single cache instance can be shared across multiple ContextParser instances so a context is normalized once and reused everywhere. Caching is disabled by default; passing no `contextCache` preserves the previous behaviour exactly. - `IContextCache` interface (hash/get/set) and a bounded LRU-backed `ContextCache` implementation, keyed on the full content of the context, its parent context, and the remaining options. - Top-level object contexts are memoized directly (their key legitimately captures the base IRI). Loaded external (URL/array) contexts are instead memoized at a base-independent boundary (`parseExternalCached`): their normalization is computed once with the per-document base replaced by a fixed, unguessable sentinel, and the real base is substituted back on each retrieval by a copy-on-write walk (`rebaseSentinel`/`replaceSentinel`) that shares base-independent subtrees, so a cached entry is never mutated. The per-call base is re-applied on a shallow clone (`applyBaseEntryCloned`). - Correctness is guaranteed: the real (base-carrying) parse is always authoritative, and a sentinel result is cached only after it is verified once, on the miss, to re-base byte-for-byte to the real parse (via `Util.deepEqual`). A context whose normalization genuinely depends on the base (relative @vocab, @type-to-@base expansion) simply fails that check and is not shared -- output stays byte-identical for every context. On the CSS boot workload the external-context hit rate goes from 0% to ~99.8% and the parse is ~1.75x faster end-to-end, with identical output. Supersedes rubensworks#70. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Creates a context caching mechanism. The use case of this is parsing numerous json-ld objects which all have the same context object; where context parsing is often the main bottleneck; see performance results below:
Parse a context that has not been cached; and without caching in place x 108 ops/sec ±0.32% (86 runs sampled)
Parse a list of iri contexts that have been cached x 79,985 ops/sec ±0.44% (90 runs sampled)
Parse a context object that has not been cached x 1,950 ops/sec ±1.36% (90 runs sampled)
Parse a context object that has been cached x 7,637 ops/sec ±0.20% (91 runs sampled)