|
| 1 | +<?php |
| 2 | + |
| 3 | +declare(strict_types=1); |
| 4 | + |
| 5 | +/** |
| 6 | + * Benchmark for the name pre-check in PropertyFetchFinder::findPropertyFetchesInClassLike(). |
| 7 | + * |
| 8 | + * When scanning a class body for fetches of one property, each PropertyFetch node ran |
| 9 | + * isInAnonymous() - which calls resolveClassReflection() - before the property name was even |
| 10 | + * compared. A class body references many different properties; every fetch of an unrelated |
| 11 | + * property paid for the anonymous-class reflection lookup only to fail the name check afterwards. |
| 12 | + * |
| 13 | + * The optimization compares the property name first and bails before isInAnonymous() on a |
| 14 | + * mismatch. |
| 15 | + * |
| 16 | + * This bench parses a class whose methods reference many distinct properties (only a few match |
| 17 | + * the target name), attaches scope, then drives findPrivatePropertyFetches() for the target |
| 18 | + * property. The nodes never change, so the calls are repeatable. |
| 19 | + * |
| 20 | + * Run on both branches to compare: |
| 21 | + * php bench/property_fetch_finder_name_bench.php |
| 22 | + * |
| 23 | + * Reports CPU time (user+sys) and wall time; CPU is the reliable metric, wall is noisy |
| 24 | + * under concurrent load. |
| 25 | + */ |
| 26 | + |
| 27 | +use PhpParser\Node\Stmt\Class_; |
| 28 | +use PhpParser\Node\Stmt\Property; |
| 29 | +use Rector\DependencyInjection\LazyContainerFactory; |
| 30 | +use Rector\PhpParser\Node\BetterNodeFinder; |
| 31 | +use Rector\PhpParser\NodeFinder\PropertyFetchFinder; |
| 32 | +use Rector\PHPStan\ScopeFetcher; |
| 33 | +use Rector\Testing\TestingParser\TestingParser; |
| 34 | + |
| 35 | +require __DIR__ . '/../vendor/autoload.php'; |
| 36 | + |
| 37 | +const METHODS = 40; // methods, each referencing many distinct properties |
| 38 | +const FETCHES_PER_METHOD = 10; // distinct other-named property fetches per method |
| 39 | +const REPEAT = 60; // outer repeats |
| 40 | + |
| 41 | +$rectorConfig = (new LazyContainerFactory())->create(); |
| 42 | +$rectorConfig->boot(); |
| 43 | + |
| 44 | +/** @var TestingParser $testingParser */ |
| 45 | +$testingParser = $rectorConfig->make(TestingParser::class); |
| 46 | + |
| 47 | +/** @var BetterNodeFinder $betterNodeFinder */ |
| 48 | +$betterNodeFinder = $rectorConfig->make(BetterNodeFinder::class); |
| 49 | + |
| 50 | +/** @var PropertyFetchFinder $propertyFetchFinder */ |
| 51 | +$propertyFetchFinder = $rectorConfig->make(PropertyFetchFinder::class); |
| 52 | + |
| 53 | +// one target property, plus many methods that read unrelated properties $this->aK |
| 54 | +$source = "<?php\n\nnamespace Bench;\n\nclass SomeClass\n{\n"; |
| 55 | +$source .= " private int \$target = 0;\n\n"; |
| 56 | +for ($i = 0; $i < METHODS; ++$i) { |
| 57 | + $source .= " public function run{$i}()\n {\n return "; |
| 58 | + $parts = []; |
| 59 | + for ($j = 0; $j < FETCHES_PER_METHOD; ++$j) { |
| 60 | + $parts[] = "\$this->a{$j}"; |
| 61 | + } |
| 62 | + $source .= implode(' + ', $parts) . ";\n }\n\n"; |
| 63 | +} |
| 64 | +$source .= " public function readTarget()\n {\n return \$this->target;\n }\n}\n"; |
| 65 | + |
| 66 | +$sourceFilePath = tempnam(sys_get_temp_dir(), 'bench_') . '.php'; |
| 67 | +file_put_contents($sourceFilePath, $source); |
| 68 | + |
| 69 | +$stmts = $testingParser->parseFileToDecoratedNodes($sourceFilePath); |
| 70 | + |
| 71 | +/** @var Class_ $class */ |
| 72 | +$class = $betterNodeFinder->findFirstInstanceOf($stmts, Class_::class); |
| 73 | + |
| 74 | +/** @var Property[] $properties */ |
| 75 | +$properties = $betterNodeFinder->findInstanceOf($class, Property::class); |
| 76 | +$targetProperty = $properties[0]; |
| 77 | + |
| 78 | +$scope = ScopeFetcher::fetch($class); |
| 79 | + |
| 80 | +$cpuSeconds = static function (): float { |
| 81 | + $usage = getrusage(); |
| 82 | + return $usage['ru_utime.tv_sec'] + $usage['ru_utime.tv_usec'] / 1_000_000 |
| 83 | + + $usage['ru_stime.tv_sec'] + $usage['ru_stime.tv_usec'] / 1_000_000; |
| 84 | +}; |
| 85 | + |
| 86 | +$run = static function () use ($propertyFetchFinder, $class, $targetProperty, $scope): void { |
| 87 | + $propertyFetchFinder->findPrivatePropertyFetches($class, $targetProperty, $scope); |
| 88 | +}; |
| 89 | + |
| 90 | +// warm up caches once, excluded from timing |
| 91 | +$run(); |
| 92 | + |
| 93 | +$wallStart = microtime(true); |
| 94 | +$cpuStart = $cpuSeconds(); |
| 95 | + |
| 96 | +for ($repeat = 0; $repeat < REPEAT; ++$repeat) { |
| 97 | + $run(); |
| 98 | +} |
| 99 | + |
| 100 | +$cpu = $cpuSeconds() - $cpuStart; |
| 101 | +$wall = microtime(true) - $wallStart; |
| 102 | + |
| 103 | +@unlink($sourceFilePath); |
| 104 | + |
| 105 | +printf('property fetches scanned: %d, repeats: %d%s', METHODS * FETCHES_PER_METHOD + 1, REPEAT, PHP_EOL); |
| 106 | +printf('CPU: %.4f s (%.3f ms/scan)%s', $cpu, $cpu / REPEAT * 1000, PHP_EOL); |
| 107 | +printf('wall: %.4f s%s', $wall, PHP_EOL); |
0 commit comments