Skip result cache re-save when nothing changed#5843
Conversation
A fully warm run (no changed, new, or deleted files) used to rewrite the entire result cache file - 10.5 MB of var_export output for a 383-file project - producing a byte-identical file every time. restore() now marks the ResultCache as up to date and process() skips the rewrite: warm-run wall time -15%, warm CPU -24% on the benchmark corpus, scaling with cache size. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
These measurement numbers seem a lot overblown. You're not saying what part of the runtime you're measuring, it's certainly not the entire analysis time. Please provide hyperfine benchmark time numbers with before/after for each of your changes, or Blackfire comparison links. |
|
Fair point, and I owe you cleaner numbers — the percentages in the description came from a custom harness and I didn't state the measurement scope. Here is the full picture, measured with hyperfine. Scope: end-to-end wall time of a fully warm run ( Setup: two clean worktrees (base = ff2647a, PR = this branch), self-analysis of phpstan-src at level 8 with a minimal config, caches primed with two runs, then
So −25 ms (1.06×) on the 383-file corpus and −77 ms (1.14×) on the 1707-file corpus, with user CPU down 21 ms resp. 67 ms. To attribute the delta I also timed the skipped work directly on the base branch (hrtime around the You're right that the −15%/−24% in the description were not transferable: same absolute saving, measured against a faster-bootstrap environment, so the relative numbers looked better than they are. I've updated the description to the hyperfine numbers above. The saving grows with result cache size — the two data points suggest roughly linear in cache bytes, so a project with a 100+ MB result cache should save a few hundred ms per warm run. I'll add hyperfine before/after numbers to the other PRs as well. |
|
I don't think this one is worth it. But the other PRs might have good ideas! |
Thanks for taking the time to consider the PRs! Claude Fable seems to be able to do quite well on finding worthwhile experiments. Happy to contribute some of my usage windows to PHPStan which is invaluable to my projects |
What & why
A fully warm run (no changed, new, or deleted files) rewrites the entire result cache
file even though the result is byte-identical to what is already on disk. For phpstan-src
itself that is an 11 MB (src/Type) to 26 MB (src) var_export file; for larger projects it
grows accordingly.
restore()now marks theResultCacheas up to date when nothing changed, andprocess()skips the save in that case. The skip only triggers when the file list,every file hash, and the project extension files all match the cache, so any change
still goes through the normal merge-and-save path.
isSaved()reports true because thecache on disk is current, which keeps the changed-extension-files warning behaviour
unchanged.
Benchmarks
hyperfine (
--warmup 3 --runs 15), end-to-end wall time of a fully warmanalyserun,self-analysis at level 8, Apple M4 Pro, PHP 8.5.7; caches primed before measuring:
src/Type(383 files, 11 MB cache)src(1707 files, 26 MB cache)Timing the skipped work directly on the base branch attributes the delta:
save()takes22.0 ms (11 MB cache) resp. 56–57 ms (26 MB cache) on a warm run, plus the
getProjectExtensionFiles()preparation the up-to-date branch also skips. The savingscales roughly linearly with result cache size. Cold runs and runs that re-analyse files
are unaffected.
Tests
make tests(12,711, green),make phpstan,make cs,make lintandmake composer-dependency-analyserall pass. Analysis output is byte-identical,including incremental (change + revert) runs, where the cache is correctly re-saved.
🤖 Generated with Claude Code