Skip to content

Cache dictionary load and parsed-expression trees for repeated parses#4

Open
ghedwards wants to merge 1 commit into
masterfrom
perf/dictionary-static-init-and-expr-cache
Open

Cache dictionary load and parsed-expression trees for repeated parses#4
ghedwards wants to merge 1 commit into
masterfrom
perf/dictionary-static-init-and-expr-cache

Conversation

@ghedwards

Copy link
Copy Markdown

Summary

  • DictionaryManager: moved the default dictionary load into a static initializer (in addition to the existing lazy initDictionaries() entry point, which is otherwise unchanged and remains idempotent). Currently the dictionary XML is re-parsed from scratch on every process start; this change lets AOT tooling (GraalVM native-image's --initialize-at-build-time) bake the already-parsed dictionaries into the build-time heap snapshot instead. Verified: with this change + --initialize-at-build-time=cfml.dictionary set on a downstream GraalVM native-image build, dictionary construction dropped to ~25ms with correct output, and the build itself succeeds cleanly.
  • CFMLParser.parseCFMLExpression: added a parse-tree cache keyed by the exact source text. Callers that re-parse textually-identical short expressions many times over (verified against a real ~8,700-line .cfc via CFLint: 2,619 calls to this method, only 1,786 distinct strings - e.g. boilerplate like var result = StructNew() repeated 43 times) now skip the lex/parse/ATN-simulation step on a cache hit. Deliberately does not cache/share the resulting CFExpression object itself - CFParsedStatement has mutable state (setParent()) that downstream callers read/rely on per-use (confirmed: CFLint's ImplicitScopeChecker stores expression references for deferred end-of-scope processing), so sharing the object across call sites would risk cross-contamination. Every cache hit gets a fresh visit() over the cached (read-only) parse tree instead, avoiding that risk entirely.

Test plan

  • Full existing test suite (mvn install on cfml.dictionary + cfml.parsing): 287 tests, 0 failures, 4 skipped (pre-existing skips, unrelated).
  • TestDictionaryManager.testExternalDictionaryLocation (exercises the custom-DictionaryPreferences override path) passes - confirms the static initializer doesn't interfere with explicit initDictionaries(prefs) calls for non-default dictionaries.
  • Verified end-to-end against a real downstream consumer (CFLint): scanned a real codebase folder before/after, JSON output byte-for-byte identical.
  • Verified the expression-tree cache is correctness-safe via a controlled A/B (cache enabled vs. disabled via an env-var toggle during development, since removed) - identical scan output either way, confirming no behavioral change, only fewer repeated parses.

- DictionaryManager: trigger the default dictionary load in a static
  initializer, not just on first explicit call. This lets AOT tooling (e.g.
  GraalVM native-image's --initialize-at-build-time) bake the parsed
  dictionaries into the build-time heap snapshot instead of re-parsing them
  on every process start. initDictionaries() itself is unchanged and
  remains safe/idempotent to call directly.

- CFMLParser.parseCFMLExpression: cache the ANTLR parse tree by source text,
  keyed on the exact expression string. Callers that re-parse
  textually-identical short expressions many times (e.g. CFLint scanning a
  file's <cfset>/<cfif> tags one at a time - common boilerplate like
  "var result = StructNew()" can repeat dozens of times in one file) skip
  the expensive lex/parse/ATN simulation on a cache hit. Deliberately does
  NOT cache/share the resulting CFExpression itself, since it has mutable
  state (CFParsedStatement.setParent()) that callers rely on per-use -
  every hit gets a fresh visit() over the cached (read-only) parse tree
  instead, so there's no risk of cross-contamination between call sites.
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.

2 participants