Skip rewriting an unchanged result cache - #6432
Conversation
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
staabm
left a comment
There was a problem hiding this comment.
Do we work with the file modification timestamp somewhere?
(Not writing the file would make the file age faster)
|
No. Cache expiration is based on |
Co-authored-by: Cursor <cursoragent@cursor.com>
|
Nice - I can reproduce a ~10% faster finish on warm runs. @SanderMuller please review |
|
Reviewed 6a243c4 against 395468a. The last fixup only adds two bashunit assertions; Does it keep the cache correct? I built a probe project and ran a mutation matrix on both branches. Analysed files: warm, body edit, new file, deleted leaf file, deleted file with dependents, config change, touch-only. Scanned files: body edit, symbol removed, used method renamed, deleted, restored. Every real change rewrites the cache on the PR. Three runs skip it, and in each of those nothing changed. A repeat warm run, a I also ran the 93 scenarios of the Gate on the head: Performance: confirmed, and a bit larger than staabm's number on my corpus. Warm A/B on a 4524-file project with a 75 MB result cache, one warmed cache per arm, three interleaved rounds. Wall medians 1.74s base against 1.55s, CPU 1.42s against 1.24s, so -11% and -13%. 3/3 rounds with no overlap in the spreads. The cache file's mtime stayed put across the PR arm's timed runs and moved on every base run. The warm error output is identical, 3265 errors on both. On @staabm's mtime question. For PHPStan itself the answer above is right, and I traced it. The periodic full analysis compares CI. Mutation Testing on 8.3 and 8.4 also fails on #6430 and #6425 from the same day, so that is the base. Octoscan I cannot pin on the base. It passed on the previous head b0d397f, which carried the same workflow edit, and on the 14 other I am not the maintainer, so the merge call is yours. |
Summary
This avoids rewriting the result cache after a warm analysis when no files need to be analysed and the cache contents are unchanged.
The cache write is skipped only when:
Any detected change continues through the existing atomic-save path.
The atomic-save E2E test now verifies both behaviours: an unchanged warm run preserves the existing cache file, while a run after a source change atomically replaces it.
Motivation
This follows the profiling reported in this discussion comment.
On a fully warm run of the profiled project, no files required analysis, but PHPStan still rewrote a 146 MB result cache. SPX attributed approximately 1.1 seconds of traced process time to
ResultCacheManager::process()andsave().Benchmark
PHPStan 2.2.14, PHP 8.5.10, warm result cache, no files analysed, six measured runs per variant:
All outputs were byte-identical. The cache modification time remained unchanged in all six runs with this change.
Tests
vendor/bin/phpunit --group exec tests/PHPStan/Command/ResultCacheInfoCommandTest.php php -d memory_limit=450M bin/phpstan analyse src/Analyser/ResultCache/ResultCacheManager.php tests/PHPStan/Command/ResultCacheInfoCommandTest.php --no-progressThis implementation and its benchmark were prepared with Cursor Agent (GPT-5.6 Sol), including adversarial reviews by Grok and GPT-5.6 Sol Medium.