🌐 US-Proxy
class="logged-out env-production page-responsive" style="word-wrap: break-word;" >
Skip to content

Performance regression: switch-heavy array-shape code ~30% slower since the type "reasons" commits (bench bug-7581.php) #14918

Description

@ondrejmirtes

Bug report

Performance regression on the 2.2.x dev branch, found by auditing the bench.yml Test job history: analysing switch-heavy code that builds array shapes key by key (bench file tests/bench/data/bug-7581.php) got ~30 % slower on 2026-06-09, and this is the largest single unexplained share of why bug-7581.php currently fails the bench job in every run (currently +63–75 % over the committed baseline on slow runners, +27–40 % even on the fastest runner class).

When it started

The regression window is exactly the "reasons" batch merged between the 11:59 and 12:23 bench runs on 2026-06-09:

  • 57ea87c835 Give reason for unresolvable PHPDoc types
  • a01080ece4 Explain unresolvable and impossible types with reasons
  • e5caec56fa Fold reasons tip into addTip closure

Evidence from the Test job (mode vs. the committed baseline.xml of 2026-05-27; both runs on same-speed runners — the unrelated borderline bench bug-9690.php reads +29.28 % / +28.99 % in the two runs, so runner speed is equal):

run commit bug-7581.php
2026-06-09 11:59 a0d97202d5 +2.88 %
2026-06-09 12:23 e5caec56fa (includes the whole batch) +35.84 %

No other file moved between those two runs.

Bimodality, then pinned

Right after the batch the effect was bimodal: two fastest-class runners on effectively identical src gave +30.06 % (2026-06-09 19:32, Mo 511.8ms vs. Mo 393.5ms) and −0.05 % (2026-06-10 09:17) — there were no src commits between those runs. That looks like the file sits right at some cliff (union-size / degradation threshold) that run-to-run noise could still tip either way. After ad34ca7395 (phpstan/phpstan-src#5848, tracked separately) landed on 2026-06-11, it has been pinned on the slow side: 44 consecutive failing runs, never once passing on any runner class since.

Local confirmation that this share is separate from phpstan/phpstan-src#5848/phpstan/phpstan-src#5876

In-process bench (median of 5 steady-state iterations, same setup as PHPStan\Benchmark\RegressionBench, PHP 8.5.5): with the later MutatingScope changes from phpstan/phpstan-src#5848 + phpstan/phpstan-src#5876 reverted on current 2.2.x HEAD, bug-7581.php improves 354 ms → 285 ms (−19 %), but is still ~+30 % above the scaled pre-June baseline — matching the share this batch introduced on CI before phpstan/phpstan-src#5848 existed.

The sample produces no errors; all the time goes into NodeScopeResolver/scope merging while $data grows a new constant-array-shape key per case, inside a foreach convergence loop over mixed. A plausible suspect is reason-string construction (AcceptsResult/IsSuperTypeOfResult reasons, the "impossible types" explanation path) now happening in hot paths even when the reasons are never displayed.

Reproduce

# in phpstan-src, 2.2.x
cp tests/bench/data/bug-7581.php test.php
bin/phpstan analyse -l 8 test.php --debug
# or the bench itself:
tests/vendor/bin/phpbench run --file=tests/bench/storage/baseline.xml --report=my-report
tests/bench/data/bug-7581.php (33-case switch building $data['A0']…$data['A32'])
<?php

namespace Bug7581;

/** @param mixed[] $parsed */
function parse(array $parsed): void
{
	$data = [];

	foreach ($parsed as $tag => $val):
		switch ($tag):
			case 'A0':
				$data['A0'] = '';
				break;
			case 'A1':
				$data['A1'] = '';
				break;
			// … identical cases A2 through A31 …
			case 'A32':
				$data['A32'] = '';
				break;
		endswitch;
	endforeach;

	echo 'test';
}

Code snippet that reproduces the problem

https://github.com/phpstan/phpstan-src/blob/2.2.x/tests/bench/data/bug-7581.php

Expected output

Analysis time of bug-7581.php back near the committed CI baseline (Mo ≈ 394 ms on GitHub runners, i.e. within the ±25 % bench tolerance) with no change in reported errors. The reasons/tips machinery should be lazy or otherwise free when no error message ends up being rendered.


Related bench regressions keeping the Test job red since 2026-06-11 (this file was later pinned permanently red by the first one): #14920 (phpstan-src#5848 guard check), #14921 (phpstan-src#5876 conditional targets), #14919 (ExprHandler dispatch refactor, same week).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions