Skip to content

Index a method body in TreePathCacher after repeated path lookup misses - #2270

Open
wmdietl wants to merge 2 commits into
masterfrom
perf-2260-nearest-enclosing
Open

wmdietl wants to merge 2 commits into
masterfrom
perf-2260-nearest-enclosing

Conversation

@wmdietl

@wmdietl wmdietl commented Oct 14, 2026

Copy link
Copy Markdown
Member

Fixes #2260.

Problem. During dataflow, AnnotatedTypeFactory.getPath searches from the path of the method body being analyzed. TreePathCacher caches only the path to the target, so every lookup for a node in the body rescans the statements before it. Finding the defaulting scope (QualifierDefaults.nearestEnclosingExceptLocal) of every node therefore makes a method body quadratic in its number of statements. JFR on the shape of checker/jtreg/nullness/Issue1438.java (4000 fields, one static method with 4000 map.put("kN", vN) calls) had 169 of 590 samples under nearestEnclosingExceptLocal.

Fix. After 32 consecutive getPath(TreePath, Tree) misses with the same leaf, TreePathCacher records a child-to-parent map of that leaf's subtree and answers later lookups under it by walking the parent chain. One map is kept, replaced when another leaf qualifies, and dropped by clear(). The path returned is the one the scan finds, including for trees that occur twice (the shared type tree of Integer a, b;): the map keeps the first occurrence in scan order.

Measurements. Nullness Checker, -proc:only, JDK 21, median of 3 interleaved runs, N calls and N fields:

N master branch
2000 8.08 s 7.57 s
4000 15.14 s 12.37 s
8000 33.99 s 25.87 s

nearestEnclosingExceptLocal falls from 169 of 590 to 3 of 448 samples at N=4000. Real code is unaffected: javacutil and dataflow sources (209 files), 5 interleaved runs, 21.9 s vs 22.2 s, within noise. Diagnostics are identical on every workload.

Tried and rejected (recorded in docs/developer/performance-notes.md): registering the CFG builder's own paths in the cacher (they skip tree levels such as MemberSelectTree and include artificial trees, so they differ from the real paths), and short-circuiting nearestEnclosingExceptLocal for declaration trees (it moves the cost to InitializationAnnotatedTypeFactory.isInitializedInTargetStore and was slower overall).

Tests. New TreePathCacherTest.manyLookupsUnderOneLeafMatchJdk compares against TreePath.getPath for every tree, cold and cached, in forward and reverse order, including trees outside the body and absent from the unit. ./gradlew :framework:test, the whole ./gradlew :checker:test (91 classes) and ./gradlew :checker:jtregTests pass.

🤖 Generated with Claude Code

During dataflow, AnnotatedTypeFactory.getPath searches from the path of the
method body being analyzed. The search caches only the path to its target, so
each lookup for a node in the body rescans the statements before it. Finding
the defaulting scope (QualifierDefaults.nearestEnclosingExceptLocal) of every
node thus makes checking a method body quadratic in its number of statements.
JFR of the Nullness Checker on a class with 4000 fields and a static method
with 4000 map.put("kN", vN) calls (the shape of checker/jtreg/nullness/
Issue1438.java) had 169 of 590 samples under nearestEnclosingExceptLocal.

After 32 consecutive getPath(TreePath, Tree) misses with the same leaf,
TreePathCacher now records a child-to-parent map of that leaf's subtree and
answers later lookups under it by walking the parent chain. One map is kept
and it is dropped by clear(). The result is the path the scan would find,
including for trees that occur twice (first occurrence in scan order).

Nullness Checker, -proc:only, JDK 21, median of 3 interleaved runs, N calls
and N fields: N=2000 8.08 -> 7.57 s, N=4000 15.14 -> 12.37 s, N=8000
33.99 -> 25.87 s. nearestEnclosingExceptLocal falls to 3 of 448 samples.
javacutil and dataflow sources (209 files), 5 interleaved runs: 21.9 -> 22.2 s,
within noise. Diagnostics are identical.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 14, 2026 04:35

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

…PathCacher

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
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.

Performance: TreePathCacher.getPath in QualifierDefaults.nearestEnclosingExceptLocal is slow for very long method bodies

2 participants