Skip to content

perf: send only cache misses to the classloader-isolated workers. - #1804

Closed
ianbrandt wants to merge 1 commit into
autonomousapps:mainfrom
ianbrandt:perf/send-only-cache-misses-to-workers
Closed

perf: send only cache misses to the classloader-isolated workers.#1804
ianbrandt wants to merge 1 commit into
autonomousapps:mainfrom
ianbrandt:perf/send-only-cache-misses-to-workers

Conversation

@ianbrandt

Copy link
Copy Markdown

Isolating kotlin-metadata to workers means InMemoryCache contents have to cross a classloader boundary. ExplodeJarTask and FindKotlinMagicTask do that by serializing every cache hit into the worker as a JSON seed, which the worker parses back into a deep copy, so a warm cache costs one full copy of the compile classpath analysis per task. This partitions in the daemon instead: hits stay there by reference, the worker receives only the artifacts it has to analyze, and the task merges the two before writing its reports.

On 40 synthetic java-library projects that all declare the same compile classpath, where 68 of the 80 explodeJar tasks have no cache misses at all:

buildHealth, medians of 4 interleaved runs

                 10-jar classpath   14-jar classpath
                     -Xmx4g            -Xmx2560m
  3.17.0              14.0s              37.5s
  main                24.0s              67.5s
  this PR             15.5s              43.5s

3.17.0 is there as the pre-isolation reference. It is the last release that used a noIsolation worker and shared the cache by reference.

A JFR recording attributes the difference. com.squareup.moshi accounts for 82% of the CPU samples that disappear, 1049 down to 639 against a total drop of 500, while com.autonomousapps.internal.asm is unchanged at 80 to 99. So the bytecode analysis is untouched and what goes away is the copying. GC pause time falls from 1054ms to 608ms, about 5% of the wall-clock difference.

The smallest -Xmx that completes drops too, from 640m to 576m on the 10-jar fixture and from 2560m to 2048m on the 14-jar one.

Isolating kotlin-metadata to workers meant `InMemoryCache` contents had to
cross a classloader boundary, so `ExplodeJarTask` and `FindKotlinMagicTask`
serialized every cache hit into the worker as a JSON seed and the worker
parsed it back into a deep copy. A `noIsolation` worker previously shared
those objects by reference in the daemon heap, so a warm cache cost one full
copy of the compile classpath analysis per task.

Partition in the daemon instead: keep hits by reference, hand the worker only
what it has to compute, and merge before writing the reports. On a 40-project
build that shares one compile classpath, this takes buildHealth from 24s to
15s, against 14s on 3.17.0. A JFR recording puts 82% of the removed CPU in
Moshi, with the ASM analysis unchanged.
@ianbrandt

Copy link
Copy Markdown
Author

This PR keeps both of the things you flagged in your comment on #1800: artifact.file.absolutePath as the cache key, and the withCoordinates rebind you called a bandaid. I don't think either can go until Coordinates can distinguish two files, so I've kept both confined to the two task actions and their merge helpers. If the classifier gets modelled, the key changes in one place per task and the rebind disappears. I'd be happy to do that follow-up.

@ljacomet

ljacomet commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Yikes, this is not something I was looking at when doing the isolation in #1719, my mistake. The concept of what you are doing here makes sense, thanks for looking into this and improving it. Note that I did not look at the code in detail.

@ianbrandt

Copy link
Copy Markdown
Author

I developed this PR with the help of Claude Code, not having seen the Code of Conduct's strict no-LLM/AI policy. Closing.

@ianbrandt ianbrandt closed this Aug 3, 2026
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