Skip to content

Commit 1dcaf15

Browse files
authored
[perf] Use native instanceof in NodeGroup::isStmtAwareNode (#8746)
1 parent be841cd commit 1dcaf15

2 files changed

Lines changed: 96 additions & 1 deletion

File tree

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* Benchmark for NodeGroup::isStmtAwareNode().
7+
*
8+
* Called per node on the traversal hot path (top call count in a full profile). It previously used
9+
* array_any() over the STMTS_AWARE class list with a closure doing a dynamic `$node instanceof $class`
10+
* per element. The change uses native instanceof via match(true): no per-element closure call, no
11+
* dynamic instanceof, same subclass semantics.
12+
*
13+
* This bench calls it over a realistic node mix - mostly non-stmt-aware nodes (the worst case, which
14+
* exhausts every instanceof) plus a couple of matches.
15+
*
16+
* Run on both branches to compare:
17+
* php bench/node_group_is_stmt_aware_bench.php
18+
*
19+
* Reports CPU time (user+sys) and wall time; CPU is the reliable metric, wall is noisy under
20+
* concurrent load.
21+
*/
22+
23+
use PhpParser\Node\Expr\Variable;
24+
use PhpParser\Node\Identifier;
25+
use PhpParser\Node\Scalar\String_;
26+
use PhpParser\Node\Stmt\ClassMethod;
27+
use PhpParser\Node\Stmt\If_;
28+
use Rector\PhpParser\Enum\NodeGroup;
29+
30+
require __DIR__ . '/../vendor/autoload.php';
31+
32+
const REPEAT = 300000;
33+
34+
$nodes = [];
35+
for ($i = 0; $i < 20; ++$i) {
36+
$nodes[] = new Variable('x');
37+
$nodes[] = new String_('s');
38+
$nodes[] = new Identifier('id');
39+
}
40+
41+
$nodes[] = new ClassMethod('m');
42+
$nodes[] = new If_(new Variable('c'));
43+
44+
$cpuSeconds = static function (): float {
45+
$usage = getrusage();
46+
return $usage['ru_utime.tv_sec'] + $usage['ru_utime.tv_usec'] / 1_000_000
47+
+ $usage['ru_stime.tv_sec'] + $usage['ru_stime.tv_usec'] / 1_000_000;
48+
};
49+
50+
$run = static function () use ($nodes): void {
51+
foreach ($nodes as $node) {
52+
NodeGroup::isStmtAwareNode($node);
53+
}
54+
};
55+
56+
// warm up
57+
$run();
58+
59+
$wallStart = microtime(true);
60+
$cpuStart = $cpuSeconds();
61+
62+
for ($repeat = 0; $repeat < REPEAT; ++$repeat) {
63+
$run();
64+
}
65+
66+
$cpu = $cpuSeconds() - $cpuStart;
67+
$wall = microtime(true) - $wallStart;
68+
69+
$totalCalls = REPEAT * count($nodes);
70+
71+
printf('nodes: %d, repeats: %d, calls: %d%s', count($nodes), REPEAT, $totalCalls, PHP_EOL);
72+
printf('CPU: %.4f s (%.4f us/call)%s', $cpu, $cpu / $totalCalls * 1_000_000, PHP_EOL);
73+
printf('wall: %.4f s%s', $wall, PHP_EOL);

‎src/PhpParser/Enum/NodeGroup.php‎

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,28 @@ final class NodeGroup
8282

8383
public static function isStmtAwareNode(Node $node): bool
8484
{
85-
return array_any(self::STMTS_AWARE, fn (string $stmtAwareClass): bool => $node instanceof $stmtAwareClass);
85+
// native instanceof instead of array_any() over STMTS_AWARE - avoids the per-element
86+
// closure call and dynamic instanceof on this hot traversal path; subclass semantics kept
87+
return match (true) {
88+
$node instanceof Block,
89+
$node instanceof Closure,
90+
$node instanceof Case_,
91+
$node instanceof Catch_,
92+
$node instanceof ClassMethod,
93+
$node instanceof Do_,
94+
$node instanceof Else_,
95+
$node instanceof ElseIf_,
96+
$node instanceof Finally_,
97+
$node instanceof For_,
98+
$node instanceof Foreach_,
99+
$node instanceof Function_,
100+
$node instanceof If_,
101+
$node instanceof Namespace_,
102+
$node instanceof TryCatch,
103+
$node instanceof While_,
104+
$node instanceof FileNode,
105+
$node instanceof Declare_ => true,
106+
default => false,
107+
};
86108
}
87109
}

0 commit comments

Comments
 (0)