Skip to content

Precompute a keyword to vocabulary index - #372

Open
Megafia wants to merge 1 commit into
microsoft:mainfrom
Megafia:vocabulary-keyword-lookup
Open

Megafia wants to merge 1 commit into
microsoft:mainfrom
Megafia:vocabulary-keyword-lookup

Conversation

@Megafia

@Megafia Megafia commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

isKeywordEnabled is called per keyword per node during validation. Each call scanned every entry of vocabularyKeywords and ran Array.prototype.includes over its keyword list.

This builds the reverse keyword -> Set<vocabUri> index once at module load, as suggested in #332, and looks the keyword up directly.

Behaviour is unchanged

Compared the old and new implementations over every possible input — all 2^14 = 16,384 subsets of active vocabularies × 63 keywords, plus the undefined case:

comparisons: 1,032,255 | mismatches: 0

The existing suite in src/test/vocabularies.test.ts passes unchanged.

No test is added. Behaviour is identical by construction, so any new test would pass on both revisions and prove nothing about this change. Two existing tests already drive the new inner loop past its first set member: type with only the 2020-12 validation vocabulary active (vocabularies.test.ts:112), and format with only format-assertion active (:148).

Performance

300,000 calls, three active 2020-12 vocabularies:

probe before after
mixed keywords 43.2 ms 3.7 ms 11.6×
core keywords only 27.9 ms 1.4 ms 20.0×

One thing beyond the issue text

The core-keyword check is hoisted above the vocabulary lookup. $ref, $id, $schema, $defs and $comment are always enabled and among the most frequently visited keywords, so they now return without any lookup at all — that is where the 20× comes from.

This is safe independently of ordering: both branches only ever return true, so the result is A || B. (They are also disjoint in the current data — no core keyword appears in any non-core vocabulary.)

Happy to drop the hoist if you would rather keep the diff to exactly what the issue describes.

Fixes #332

`isKeywordEnabled` runs per keyword per node during validation. Each call
scanned every entry of `vocabularyKeywords` and ran `includes` over its
keyword array.

Build the reverse index once at module load and look the keyword up
directly. The core-keyword check moves above the lookup, since core
keywords are among the most frequently visited and need no lookup at all;
both branches only ever return true, so the order does not affect the
result.

Fixes microsoft#332
@Megafia
Megafia marked this pull request as ready for review September 13, 2026 23:32
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.

2019-09: isKeywordEnabled does an O(vocabularies) scan per keyword

1 participant