Skip to content

Faster MutatingScope->getNodeKey() - #5014

Merged
staabm merged 4 commits into
phpstan:2.1.xfrom
staabm:node-key
Feb 24, 2026
Merged

staabm merged 4 commits into
phpstan:2.1.xfrom
staabm:node-key

Conversation

@staabm

@staabm staabm commented Feb 21, 2026 •

Copy link
Copy Markdown
Contributor

saves 0,3-0,4 seconds on php bin/phpstan analyse src/Analyser/ src/Rules/ -v --debug.
also reduces memory consumption by a few megabytes (because not every variable needs to hold a expr-printed attribute).

getNodeKey is invoked by-far most often with Variable, see numer of invocations per node-type:

array(67) {
  ["PHPStan\Node\Expr\IntertwinedVariableByReferenceWithExpr"]=>
  int(4)
  ["PhpParser\Node\Expr\BinaryOp\LogicalXor"]=>
  int(5)
  ["PhpParser\Node\Expr\PostDec"]=>
  int(8)
  ["PhpParser\Node\Expr\AssignOp\Minus"]=>
  int(8)
  ["PHPStan\Node\IssetExpr"]=>
  int(12)
  ["PhpParser\Node\Expr\Throw_"]=>
  int(18)
  ["PhpParser\Node\Expr\ErrorSuppress"]=>
  int(25)
  ["PhpParser\Node\Expr\BinaryOp\Mod"]=>
  int(34)
  ["PhpParser\Node\Expr\BitwiseNot"]=>
  int(35)
  ["PhpParser\Node\Expr\BinaryOp\BitwiseOr"]=>
  int(38)
  ["PhpParser\Node\Expr\BinaryOp\Mul"]=>
  int(41)
  ["PhpParser\Node\Expr\BinaryOp\Spaceship"]=>
  int(46)
  ["PhpParser\Node\Expr\PreDec"]=>
  int(46)
  ["PhpParser\Node\Expr\Cast\Int_"]=>
  int(56)
  ["PhpParser\Node\Expr\Clone_"]=>
  int(60)
  ["PhpParser\Node\Expr\Cast\Bool_"]=>
  int(64)
  ["PhpParser\Node\Expr\AssignOp\BitwiseOr"]=>
  int(79)
  ["PhpParser\Node\Expr\AssignOp\Plus"]=>
  int(85)
  ["PhpParser\Node\Expr\List_"]=>
  int(98)
  ["PhpParser\Node\Expr\BinaryOp\SmallerOrEqual"]=>
  int(115)
  ["PhpParser\Node\Expr\BinaryOp\Equal"]=>
  int(121)
  ["PhpParser\Node\Expr\AssignOp\Concat"]=>
  int(148)
  ["PhpParser\Node\Expr\StaticPropertyFetch"]=>
  int(163)
  ["PhpParser\Node\Expr\PostInc"]=>
  int(230)
  ["PhpParser\Node\Expr\Include_"]=>
  int(235)
  ["PhpParser\Node\Expr\AssignOp\Coalesce"]=>
  int(275)
  ["PhpParser\Node\Expr\UnaryMinus"]=>
  int(311)
  ["PhpParser\Node\Expr\Closure"]=>
  int(408)
  ["PhpParser\Node\Expr\BinaryOp\BitwiseAnd"]=>
  int(436)
  ["PhpParser\Node\Expr\BinaryOp\GreaterOrEqual"]=>
  int(478)
  ["PhpParser\Node\Expr\PreInc"]=>
  int(784)
  ["PhpParser\Node\Expr\BinaryOp\Smaller"]=>
  int(793)
  ["PhpParser\Node\Expr\BinaryOp\Minus"]=>
  int(1031)
  ["PHPStan\Node\Expr\AlwaysRememberedExpr"]=>
  int(1161)
  ["PhpParser\Node\Expr\BinaryOp\Plus"]=>
  int(1199)
  ["PhpParser\Node\Expr\ArrowFunction"]=>
  int(1211)
  ["PhpParser\Node\Expr\Cast\String_"]=>
  int(1710)
  ["PhpParser\Node\Expr\BinaryOp\Greater"]=>
  int(1941)
  ["PhpParser\Node\Scalar\Float_"]=>
  int(2085)
  ["PHPStan\Node\Expr\OriginalForeachKeyExpr"]=>
  int(2157)
  ["PhpParser\Node\Expr\BinaryOp\Concat"]=>
  int(2303)
  ["PHPStan\Node\Expr\TypeExpr"]=>
  int(2689)
  ["PhpParser\Node\Expr\BinaryOp\Coalesce"]=>
  int(3159)
  ["PhpParser\Node\Expr\Ternary"]=>
  int(3611)
  ["PHPStan\Node\Expr\ParameterVariableOriginalValueExpr"]=>
  int(3730)
  ["PhpParser\Node\Expr\Isset_"]=>
  int(3837)
  ["PHPStan\Node\Expr\PropertyInitializationExpr"]=>
  int(4632)
  ["PhpParser\Node\Expr\BinaryOp\BooleanOr"]=>
  int(9840)
  ["PHPStan\Node\Expr\ForeachValueByRefExpr"]=>
  int(10580)
  ["PhpParser\Node\Expr\BinaryOp\NotIdentical"]=>
  int(11218)
  ["PhpParser\Node\Expr\New_"]=>
  int(12485)
  ["PhpParser\Node\Expr\BinaryOp\BooleanAnd"]=>
  int(23353)
  ["PhpParser\Node\Scalar\Int_"]=>
  int(24669)
  ["PhpParser\Node\Expr\BooleanNot"]=>
  int(24972)
  ["PhpParser\Node\Expr\Instanceof_"]=>
  int(25872)
  ["PhpParser\Node\Expr\Array_"]=>
  int(29885)
  ["PhpParser\Node\Expr\ClassConstFetch"]=>
  int(29916)
  ["PhpParser\Node\Expr\Assign"]=>
  int(33054)
  ["PhpParser\Node\Expr\BinaryOp\Identical"]=>
  int(46624)
  ["PhpParser\Node\Scalar\String_"]=>
  int(62483)
  ["PhpParser\Node\Expr\StaticCall"]=>
  int(67364)
  ["PhpParser\Node\Expr\FuncCall"]=>
  int(68771)
  ["PhpParser\Node\Expr\ArrayDimFetch"]=>
  int(76027)
  ["PhpParser\Node\Expr\ConstFetch"]=>
  int(76787)
  ["PhpParser\Node\Expr\PropertyFetch"]=>
  int(170030)
  ["PhpParser\Node\Expr\MethodCall"]=>
  int(475115)
  ["PhpParser\Node\Expr\Variable"]=>
  int(1037687)
}

grafik

@staabm
staabm marked this pull request as ready for review February 21, 2026 09:21
@phpstan-bot

Copy link
Copy Markdown
Collaborator

This pull request has been marked as ready for review.

@staabm staabm closed this Feb 21, 2026
@staabm
staabm deleted the node-key branch February 21, 2026 09:27
@staabm staabm reopened this Feb 21, 2026

@VincentLanglet VincentLanglet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about this one.

Is it the right place to do this ? I mean, this early if seems to avoid

  • calling printExpr
  • two if conditions which seems really quick

So if the printExpr is the slow method to skip, we could have the early return inside to improve the situation more often ? Cause their is multiple other call to printExpr.

@staabm
staabm marked this pull request as draft February 23, 2026 14:19
Comment on lines +920 to +923
// perf optimize for the most common path
if ($node instanceof Variable && !$node->name instanceof Expr) {
return '$' . $node->name;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I duplicated the logic to here now, as getNodeKey is by far the most common caller of exprPrinter->printExpr:

grafik

and with this check here, we also prevent unnecessary getAttributes calls below

@staabm
staabm marked this pull request as ready for review February 24, 2026 09:23
@phpstan-bot

Copy link
Copy Markdown
Collaborator

This pull request has been marked as ready for review.

@staabm

staabm commented Feb 24, 2026 •

Copy link
Copy Markdown
Contributor Author

running this change on

blackfire run --ignore-exit-status php bin/phpstan analyze src/Analyser --debug -v --level=5

leads the following difference

grafik

we see the memory reduction because exprPrinter->printExpr puts a cache into every AstNode attributes property.

@staabm
staabm merged commit b8492b8 into phpstan:2.1.x Feb 24, 2026
639 of 642 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants