Skip to content

Don't allocate a VisitorState when a memoized lookup hits the cache - #6078

Closed
vlsi wants to merge 1 commit into
google:masterfrom
vlsi:vs/memoize-no-alloc-on-hit
Closed

Don't allocate a VisitorState when a memoized lookup hits the cache#6078
vlsi wants to merge 1 commit into
google:masterfrom
vlsi:vs/memoize-no-alloc-on-hit

Conversation

@vlsi

@vlsi vlsi commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Why

VisitorState.Cache.get replaced the caller's state with a pathless copy before it looked in the cache:

public synchronized T get(VisitorState state) {
  state = state.withNoPathForMemoization();

  T value = cache.get();
  if (value == null) {
    value = impl.get(state);
    ...

The copy keeps a memoized supplier from reading a TreePath that belongs to one compilation unit while its result is cached for the whole compilation, so impl.get is the only caller that needs it. The cache answers most reads without calling impl, and those reads allocated a VisitorState and dropped it.

The scanner reaches memoized suppliers through TypePredicates, Suppliers.typeFromString, Matchers, and MethodMatchers, once per matcher per AST node, so the reads are frequent: compiling the 948 sources of error_prone_core with Error Prone enabled runs Cache.get 25448504 times and answers 17019874 of those from the cache.

What

compute now makes the pathless copy, and it runs only when the value has to be computed. withNoPathForMemoization returns this when the path is already null, so a memoized supplier that reads another memoized value allocates nothing either.

impl still receives a state whose getPath() throws, and the provenance bookkeeping reads sharedState, which the copy shares with the original.

How to verify

mvn -pl check_api,core test

Allocation was measured with an in-process javac compiling the 948 sources of error_prone_core with Error Prone enabled on JDK 24, reading ThreadMXBean.getThreadAllocatedBytes for the compiling thread. Steady state over six compilations in one JVM: 8.251 GiB before, 7.744 GiB after. The 17019874 cached reads at 32 bytes per VisitorState come to 0.507 GiB, which is the whole difference.

@google-cla

google-cla Bot commented Aug 31, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

VisitorState.Cache.get replaced the caller's state with a pathless copy before
it looked in the cache, so every read of a memoized value allocated a
VisitorState, including the reads the cache answered on its own. Only impl.get
needs that copy, so compute now makes it.

Compiling the 948 sources of error_prone_core with Error Prone enabled, the
compiling thread allocates 7.74 GiB instead of 8.25 GiB. Cache.get runs
25448504 times over that compilation and answers 17019874 of those from the
cache, which at 32 bytes per VisitorState accounts for the whole difference.

Assisted-by: Claude Code (claude-opus-5)
@vlsi
vlsi force-pushed the vs/memoize-no-alloc-on-hit branch from d2eb1e4 to e91d4c0 Compare August 31, 2026 07:51

@cpovirk cpovirk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

FWIW, an internal benchmark on com.google.common.collect doesn't show any improvement in wall time, CPU time, or peak memory. But things can be different when compiling different code and when compiling with different settings, and steady-state memory matters, too. This seems straightforward enough to be worth trying.

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