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

Performance regression: keeping subtype-absorbed conditional targets (phpstan-src#5876) adds ~15% on constant-array loop code (bench bug-10538.php) #14921

Description

@ondrejmirtes

Bug report

Performance regression on the 2.2.x dev branch, found by auditing the bench.yml Test job history. c53a7b8a5f (phpstan/phpstan-src#5876, Keep subtype-absorbed variables as conditional-expression targets when merging branches) added a second ~+15 percentage-point step on top of the already-regressed tests/bench/data/bug-10538.php bench (nested foreach over a ~100-entry constant array of string array shapes).

Where the previous unset($newVariableTypes[$exprString]) dropped a subtype-absorbed variable entirely, it now stays as a conditional-expression target — so more conditional-expression holders are created and carried through every subsequent branch merge in loop convergence.

When it started

Consistent step between the 2026-06-19 and 2026-06-20 bench runs, across all runner speed classes (mode vs. the committed baseline.xml of 2026-05-27; classes identified via the stable bug-7581.php percentage as a runner-speed proxy):

runner class before Jun 20 after Jun 20
fast passing (< +10 %) +15.5–16.7 %
medium ~+19 % +33–36 %
slow +26–29 % +42–46 %

c53a7b8a5f is the only src/ commit between the last pre-step observation (2026-06-19 13:33, fast-class runner, bug-10538.php passing) and the first post-step observation (2026-06-20 09:03 — the run for c53a7b8a5f itself, medium-class runner, bug-10538.php at +33.74 %). Since the bench's tolerance tier is ±10 % (baseline 2.61 s), the file now fails on every runner class except the very fastest.

Local confirmation

In-process bench on current 2.2.x HEAD (median of 5 steady-state iterations, same setup as PHPStan\Benchmark\RegressionBench, PHP 8.5.5), restoring the pre-phpstan/phpstan-src#5876 unset behavior:

bench HEAD phpstan/phpstan-src#5876 reverted both phpstan/phpstan-src#5876 + phpstan/phpstan-src#5848 reverted
bug-10538.php 2 037.5 ms 1 830.5 ms (−10 %) 1 603.8 ms (−21 %)
bug-14462.php 114.9 ms 114.6 ms (0) 90.5 ms (−21 %)

The bug-14462.php row shows the interaction with ad34ca7395 (phpstan/phpstan-src#5848, tracked separately): the extra targets kept by this change are exactly the holders on which phpstan/phpstan-src#5848's extra reverse isSuperTypeOf() guard check then runs, so the two compound — reverting either one alone recovers nothing on that file, reverting both recovers −21 %.

Reproduce

# in phpstan-src, 2.2.x
cp tests/bench/data/bug-10538.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-10538.php (abbreviated — full file linked below)
<?php

namespace Bug10538;

class SomeClass {
	protected const STATES = [
		'BW' => 1, 'BY' => 2, 'BE' => 3, 'BB' => 4,
		'HB' => 5, 'HH' => 6, 'HE' => 7, 'MV' => 8,
		'NI' => 9, 'NW' => 10, 'RP' => 11, 'SL' => 12,
		'ST' => 13, 'SN' => 14, 'SH' => 15, 'TH' => 16,
	];

	protected static function test(): void
	{
		for ($i = 0; $i < 10; $i++) {
			foreach (self::CHANGESET as $stateCode => $changesets) {
				$stateId = self::STATES[$stateCode];
				foreach ($changesets as $changeset) {
					echo sprintf(
						'%s %s %s %s',
						$changeset['new']['Gemarkung'],
						$changeset['old']['Gemeinde'],
						$changeset['old']['Gemarkung'],
						$stateId
					);
				}
			}
		}
	}

	// CHANGESET: ~100-entry constant array of nested string array shapes
	protected const CHANGESET = [ /* … */ ];
}

Code snippet that reproduces the problem

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

Expected output

The inference improvement from phpstan/phpstan-src#5876 kept, but without the ~15 % analysis-time cost on constant-array-heavy loop code — e.g. by bounding which subtype-absorbed variables are worth keeping as conditional targets, or making the extra holders cheap for the downstream merge/guard machinery (see the interaction with phpstan/phpstan-src#5848). bug-10538.php back within the ±10 % bench tolerance on typical runners.


Related bench regressions keeping the Test job red since 2026-06-11: #14920 (phpstan-src#5848 — compounds with this change on bug-14462.php), #14918 (type "reasons" commits), #14919 (ExprHandler dispatch refactor).

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