Repository navigation
Skip the get_included_files() scan in the autoload trap while OPcache compiled nothing - #6979
Open
ondrejmirtes wants to merge 1 commit into
Open
ondrejmirtes wants to merge 1 commit into
ondrejmirtes wants to merge 1 commit into
Conversation
… compiled nothing invalidateIncludedFiles() copied every included path before each autoloader that runs in the trap, so the cost of a class lookup grew with the number of files a bootstrap included. It now compares OPcache's hits and misses with the ones it saw at its last scan and skips the scan when they are equal. The trap's own compiles, which the loop after the trap invalidates, do not count. Amp-Thread-ID: https://ampcode.com/threads/T-01a1b250-1b0b-7741-b14c-5cf983afb658
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#6742 made
AutoloadSourceLocator::invalidateIncludedFiles()callget_included_files()before every autoloader that runs in the autoload trap. That call copies every included path, so each class lookup that reaches a non-Composer autoloader costs time proportional to the number of included files. PHPStan 2.3 restarts itself with OPcache on, so this path is active by default. A project bootstrap, such as a Symfony kernel boot, includes thousands of files.The invalidation is only needed after something got compiled into OPcache since the last scan. With this change,
invalidateIncludedFiles()reads OPcache'shitsandmissesfromopcache_get_status(false), which costs about 1 µs against about 55 µs forget_included_files()with 10,000 included files. When the counters equal the ones from its last scan, it skips the scan. A script gets into OPcache's shared memory, or runs from it, only through a miss or a hit, so equal counters mean there is nothing new to invalidate. The existing cursor still makes sure each included file is invalidated at most once.After the trap, the loop that invalidates the trap's empty scripts now also counts those compiles as handled, provided there was no hit in between. OPcache caches only
file://andphar://paths, and the trap overrides both, so every miss inside the trap is one of its own scripts. Without this, every class that a trapped autoloader finds would cause a scan at the next lookup.When the counters are not available, it scans as before. That is the case with
opcache.restrict_api, withopcache.file_cache_only, and withopcache.file_cacheconfigured, because a script loaded from the file cache counts as neither a hit nor a miss. PHPStan's restart blanksopcache.file_cache.e2e/bootstrap-autoloader-wrong-filenow first looks up a class that has no file, which runs a scan, and then the class whose file the bootstrap autoloader includes for real outside the trap. If the counter check skipped scans after the first one, this would die with "Cannot redeclare class". I checked that: with that mutation the new test crashes, and the old test still passes.Measurements
Reproducer, level 0:
bootstrapFiles: [bootstrap.php].bootstrap.phpregistersspl_autoload_register(static function (string $class): void {});andrequire_oncesinc/f0.php…inc/f9999.php, each declaring one function.test.phpis one class with 1,000 methodspublic function mN(): void { new \Missing\ClassN(); }.The numbers are from
analyse --debug -vvv: the per-filetooktime and thetotalpeak memory, best of 5 runs, on PHP 8.4.27 without the Turbo extension.opcache.enable_cli=1,validate_timestamps=0,file_cache=, JIT off).--debugruns the analysis in that main process, so the trap runs with OPcache active.bin/phpstanwith the samevendor/), not from a PHAR, so compare them with each other rather than with the release PHARs.invalidateIncludedFiles()removed entirely, the same checkout measured 1.01–1.06 s, the same as this PR. In this run, this PR scansget_included_files()twice instead of once per lookup.