Skip to content

Resolve constant offsets on constant arrays through the key index - #6442

Merged
ondrejmirtes merged 1 commit into
2.3.xfrom
perf/constant-array-key-index
Sep 14, 2026
Merged

Resolve constant offsets on constant arrays through the key index#6442
ondrejmirtes merged 1 commit into
2.3.xfrom
perf/constant-array-key-index

Conversation

@ondrejmirtes

Copy link
Copy Markdown
Member

Problem

ConstantArrayType::isSuperTypeOf() asks the other array about every key with hasOffsetValueType() and getOffsetValueType(), and both scanned the whole key list with isSuperTypeOf() per key, so comparing two array shapes was quadratic in their size.

Since 29c49f2 / 77527df every $params['key'] ?? null re-specifies $params, and the scope's conditional-expression matching compares the shape against every recorded condition on it. A method with 85 such reads over an 85-key optional array shape (Slevomat's GeneratePremiseTextsAiExcellenceResult::create()) spent ~10 s in ConstantStringType::isSuperTypeOf (119M calls in a single-file SPX profile) and was the whole measurable 2.3.x slowdown on that project.

Change

Constant integer/string offsets now resolve through the existing value-keyed getKeyIndexMap() in recursiveHasOffsetValueType() and getOffsetValueType(): a hit is verified with isSuperTypeOf() before it is used, a miss keeps the full scan (which also covers the unsealed extras), and isOptionalKey() reads a set instead of in_array() over the optional keys.

Verification

  • Hot file: 12.3 s → 3.5 s single-process (the pre-regression build took 3.9 s); synthetic reproducer 12.4 s → 2.3 s.
  • Full Slevomat run via locally built PHAR (fork + turbo), interleaved pairs: 594 s / 592 s vs 623 s / 611 s user CPU; output byte-identical.
  • tests/PHPStan/Type, tests/PHPStan/Analyser, tests/PHPStan/Rules green; phpcs and self-analysis clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GvDxWpzXzticTTm5eLCsb2

ConstantArrayType::isSuperTypeOf() asks the other array about every key
with hasOffsetValueType() and getOffsetValueType(), and both scanned the
whole key list with isSuperTypeOf() per key, so comparing two array shapes
was quadratic in their size. The scope's conditional-expression matching
compares the shape of a variable against every recorded condition on it,
which made a method with dozens of `$params['key'] ?? null` reads over a
large optional array shape spend seconds in ConstantStringType::isSuperTypeOf.

Constant integer and string offsets now resolve through the existing
value-keyed index: a hit is verified with isSuperTypeOf() before it is used,
a miss keeps the full scan, and isOptionalKey() reads a set instead of
in_array() over the optional keys.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvDxWpzXzticTTm5eLCsb2
@phpstan-bot

Copy link
Copy Markdown
Collaborator

You've opened the pull request against the latest branch 2.3.x. PHPStan 2.3 is not going to be released for months. If your code is relevant on 2.2.x and you want it to be released sooner, please rebase your pull request and change its target to 2.2.x.

@ondrejmirtes
ondrejmirtes merged commit 70d5b9d into 2.3.x Sep 14, 2026
240 of 243 checks passed
@ondrejmirtes
ondrejmirtes deleted the perf/constant-array-key-index branch September 14, 2026 11:07
@staabm

staabm commented Sep 14, 2026

Copy link
Copy Markdown
Contributor
  • Hot file: 12.3 s → 3.5 s single-process (the pre-regression build took 3.9 s); synthetic reproducer 12.4 s → 2.3 s.

@ondrejmirtes should we have a phpbench script for this case?

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