|
| 1 | +<?php |
| 2 | + |
| 3 | +declare(strict_types=1); |
| 4 | + |
| 5 | +/** |
| 6 | + * Benchmark for CallLikeReflectionNodeVisitor::enterNode() on calls with array args. |
| 7 | + * |
| 8 | + * The visitor resolved the function-like reflection for every call with an array literal arg, |
| 9 | + * to mark arrays passed to a parameter that cannot hold a Closure (IS_ARG_NOT_ACCEPTING_CLOSURE). |
| 10 | + * That attribute is only read by ArrayToFirstClassCallableRector, which bails on any array that |
| 11 | + * does not have exactly 2 items, so calls like in_array($x, ['a', 'b', 'c']) or |
| 12 | + * $this->foo(['key' => $value]) paid for the resolve and decorated nothing useful. |
| 13 | + * |
| 14 | + * The optimization narrows the node-only pre-check to 2-item arrays (the [$caller, 'method'] |
| 15 | + * shape), so other array args bail before resolveFunctionLikeReflectionFromCall. |
| 16 | + * |
| 17 | + * This bench parses a file full of calls with non-callable array args, attaches scope, |
| 18 | + * then drives enterNode() over every CallLike node. |
| 19 | + * |
| 20 | + * Run on both branches to compare: |
| 21 | + * php bench/call_like_reflection_visitor_array_arg_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\Expr\CallLike; |
| 28 | +use Rector\DependencyInjection\LazyContainerFactory; |
| 29 | +use Rector\PhpParser\Node\BetterNodeFinder; |
| 30 | +use Rector\PhpParser\NodeVisitor\CallLikeReflectionNodeVisitor; |
| 31 | +use Rector\Testing\TestingParser\TestingParser; |
| 32 | + |
| 33 | +require __DIR__ . '/../vendor/autoload.php'; |
| 34 | + |
| 35 | +const METHODS = 60; // methods, each with a handful of calls with array args |
| 36 | +const REPEAT = 40; // outer repeats over the whole call set |
| 37 | + |
| 38 | +$rectorConfig = (new LazyContainerFactory())->create(); |
| 39 | +$rectorConfig->boot(); |
| 40 | + |
| 41 | +/** @var TestingParser $testingParser */ |
| 42 | +$testingParser = $rectorConfig->make(TestingParser::class); |
| 43 | + |
| 44 | +/** @var BetterNodeFinder $betterNodeFinder */ |
| 45 | +$betterNodeFinder = $rectorConfig->make(BetterNodeFinder::class); |
| 46 | + |
| 47 | +$visitor = $rectorConfig->make(CallLikeReflectionNodeVisitor::class); |
| 48 | + |
| 49 | +// a class with many methods that make calls with non-callable array args |
| 50 | +$source = "<?php\n\nnamespace Bench;\n\nclass SomeClass\n{\n"; |
| 51 | +for ($i = 0; $i < METHODS; ++$i) { |
| 52 | + $source .= " public function run{$i}(string \$value, array \$items)\n {\n"; |
| 53 | + $source .= " \$exists = in_array(\$value, ['a', 'b', 'c'], true);\n"; |
| 54 | + $source .= " \$merged = array_merge(\$items, ['key' => \$value]);\n"; |
| 55 | + $source .= " \$joined = implode(',', [\$value, \$value, \$value]);\n"; |
| 56 | + $source .= " \$this->helper(\$value, []);\n"; |
| 57 | + $source .= " return \$exists;\n"; |
| 58 | + $source .= " }\n\n"; |
| 59 | +} |
| 60 | +$source .= " public function helper(string \$value, array \$options)\n {\n }\n}\n"; |
| 61 | + |
| 62 | +$sourceFilePath = tempnam(sys_get_temp_dir(), 'bench_') . '.php'; |
| 63 | +file_put_contents($sourceFilePath, $source); |
| 64 | + |
| 65 | +$stmts = $testingParser->parseFileToDecoratedNodes($sourceFilePath); |
| 66 | + |
| 67 | +/** @var CallLike[] $callLikes */ |
| 68 | +$callLikes = $betterNodeFinder->findInstanceOf($stmts, CallLike::class); |
| 69 | + |
| 70 | +$cpuSeconds = static function (): float { |
| 71 | + $usage = getrusage(); |
| 72 | + return $usage['ru_utime.tv_sec'] + $usage['ru_utime.tv_usec'] / 1_000_000 |
| 73 | + + $usage['ru_stime.tv_sec'] + $usage['ru_stime.tv_usec'] / 1_000_000; |
| 74 | +}; |
| 75 | + |
| 76 | +$run = static function () use ($visitor, $callLikes): void { |
| 77 | + foreach ($callLikes as $callLike) { |
| 78 | + $visitor->enterNode($callLike); |
| 79 | + } |
| 80 | +}; |
| 81 | + |
| 82 | +// warm up caches once, excluded from timing |
| 83 | +$run(); |
| 84 | + |
| 85 | +$wallStart = microtime(true); |
| 86 | +$cpuStart = $cpuSeconds(); |
| 87 | + |
| 88 | +for ($repeat = 0; $repeat < REPEAT; ++$repeat) { |
| 89 | + $run(); |
| 90 | +} |
| 91 | + |
| 92 | +$cpu = $cpuSeconds() - $cpuStart; |
| 93 | +$wall = microtime(true) - $wallStart; |
| 94 | + |
| 95 | +@unlink($sourceFilePath); |
| 96 | + |
| 97 | +$totalCalls = REPEAT * count($callLikes); |
| 98 | + |
| 99 | +printf('CallLike nodes: %d, repeats: %d, enterNode() calls: %d%s', count($callLikes), REPEAT, $totalCalls, PHP_EOL); |
| 100 | +printf('CPU: %.4f s (%.3f us/call)%s', $cpu, $cpu / $totalCalls * 1_000_000, PHP_EOL); |
| 101 | +printf('wall: %.4f s%s', $wall, PHP_EOL); |
0 commit comments