Skip to content

Improves test suite performance by 8.5-25x [experiment!] - #22917

Draft
NickSdot wants to merge 24 commits into
php:masterfrom
NickSdot:perf/run-tests
Draft

Improves test suite performance by 8.5-25x [experiment!]#22917
NickSdot wants to merge 24 commits into
php:masterfrom
NickSdot:perf/run-tests

Conversation

@NickSdot

@NickSdot NickSdot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

While working on #22799 I was bothered by the test suite being so slow. CI needs ~45 minutes from push to finish; running tests locally also takes quite a while. I was wondering why that is and whether it must be like that. Hence, I decided to run an experiment to find out if we could make the developer experience notably better.

Note

This experiment was fully executed by Codex. I was throwing some money on OpenAI to let GPT 5.6 Sol pursue the /goal to make the test suite "massively" faster. This PR is the result of Codex trying exactly that -- for 14 hours straight. Consequently, I will not pretend that I understand the implications of every proposal -- and I also did not review everything in detail yet. Though, I did spend plenty of time to confirm the claims and reworking things.

The result are interesting enough to open this clearly flagged as LLM-executed experiment, hoping that it can serve as a discussion starter to find out whether anything can be picked up. Would love to get feedback from people with more experience than me, to pin down which of these are legit ideas and which are dead-ends right away.

Extracted PRs

All canges are now extracted to logical PRs as outlined below.

PR title status
#22957 Run test subprocesses without shell open
#22939 Run tests in parallel by default open
#22942 Adds CLI test fork server for improved test suite performance open
#22944 Cache failed PDO and SNMP service probes open
#22945 Add bounded test directory concurrency open
#22946 Select Windows test workers automatically open
#22947 Parallelize new_oom subprocesses open
#22948 Replace fixed test delays with deterministic synchronization open
#22949 Make socket tests robust in restricted environments open
#22950 Reduce expensive test fixtures open

Surfaced fixes extracted to separate PRs

PR title status
#22934 Run scandir overflow test on Windows only merged
#22911 Fixes leaked System V shared-memory segments in tests merged
#22912 Prevents tests from leaking System V IPC objects merged
#22952 Fix redirected-test progress accounting in parallel runs open

Results

Values in seconds; measured on macOS. The full suite improvements are already awesome on their own. But targeted runs also show up to 25x faster results (-j10 vs. -j10), many at least 2x faster, and very few slightly slower.

Full Suite

flag master here gain
(none) 2,223.224 69.833 -2,153.39 (31.84x) auto-detected concurrency by default
-j1 2,224.902 443.214 -1,781.69 (5.02x) optionally opting out of concurrency
-j10 594.445 69.768 -524.68 (8.52x)

Partial Runs

target flag master here gain
ext/bcmath (none) 10.912 1.033 -9.88s (10.56x)
-j1 10.912 2.038 -8.87s (5.35x)
-j10 2.477 0.953 -1.52s (2.60x)
ext/bz2 (none) 2.537 0.596 -1.94s (4.26x)
-j1 2.537 0.507 -2.03s (5.00x)
-j10 0.925 0.663 -0.26s (1.40x)
ext/calendar (none) 3.896 0.757 -3.14s (5.15x)
-j1 3.896 1.220 -2.68s (3.19x)
-j10 1.048 0.702 -0.35s (1.49x)
ext/com_dotnet (none) 0.119 0.354 +0.23s (0.34x)
-j1 0.119 0.179 +0.06s (0.66x)
-j10 0.299 0.353 +0.05s (0.85x)
ext/ctype (none) 3.176 0.625 -2.55s (5.08x)
-j1 3.176 0.676 -2.50s (4.70x)
-j10 1.031 0.636 -0.39s (1.62x)
ext/curl (none) 34.101 6.639 -27.46s (5.14x)
-j1 34.101 23.905 -10.20s (1.43x)
-j10 35.727 6.655 -29.07s (5.37x)
ext/date (none) 44.032 2.711 -41.32s (16.24x)
-j1 44.032 10.187 -33.84s (4.32x)
-j10 9.308 2.487 -6.82s (3.74x)
ext/dba (none) 5.617 0.791 -4.83s (7.10x)
-j1 5.617 0.910 -4.71s (6.17x)
-j10 3.134 0.853 -2.28s (3.67x)
ext/dom (none) 54.535 1.915 -52.62s (28.48x)
-j1 54.535 6.628 -47.91s (8.23x)
-j10 10.087 1.826 -8.26s (5.52x)
ext/enchant (none) 0.122 0.304 +0.18s (0.40x)
-j1 0.122 0.127 +0.01s (0.96x)
-j10 0.308 0.314 +0.01s (0.98x)
ext/exif (none) 6.351 0.811 -5.54s (7.83x)
-j1 6.351 1.196 -5.16s (5.31x)
-j10 1.358 0.916 -0.44s (1.48x)
ext/ffi (none) 6.710 0.850 -5.86s (7.89x)
-j1 6.710 1.202 -5.51s (5.58x)
-j10 1.576 1.154 -0.42s (1.37x)
ext/fileinfo (none) 5.251 0.876 -4.38s (5.99x)
-j1 5.251 1.696 -3.56s (3.10x)
-j10 1.551 0.997 -0.55s (1.56x)
ext/filter (none) 7.543 0.915 -6.63s (8.24x)
-j1 7.543 2.415 -5.13s (3.12x)
-j10 1.926 1.080 -0.85s (1.78x)
ext/ftp (none) 7.842 1.682 -6.16s (4.66x)
-j1 7.842 3.100 -4.74s (2.53x)
-j10 2.288 1.557 -0.73s (1.47x)
ext/gd (none) 25.575 1.503 -24.07s (17.02x)
-j1 25.575 3.430 -22.14s (7.46x)
-j10 6.162 1.493 -4.67s (4.13x)
ext/gettext (none) 1.783 0.579 -1.20s (3.08x)
-j1 1.783 0.511 -1.27s (3.49x)
-j10 0.740 0.636 -0.10s (1.16x)
ext/gmp (none) 8.072 1.131 -6.94s (7.14x)
-j1 8.072 2.373 -5.70s (3.40x)
-j10 2.075 1.134 -0.94s (1.83x)
ext/hash (none) 4.881 0.611 -4.27s (7.99x)
-j1 4.881 0.918 -3.96s (5.32x)
-j10 1.100 0.493 -0.61s (2.23x)
ext/iconv (none) 5.313 0.953 -4.36s (5.58x)
-j1 5.313 2.612 -2.70s (2.03x)
-j10 1.420 1.023 -0.40s (1.39x)
ext/intl (none) 32.200 4.522 -27.68s (7.12x)
-j1 32.200 7.158 -25.04s (4.50x)
-j10 6.659 1.910 -4.75s (3.49x)
ext/json (none) 6.589 0.609 -5.98s (10.82x)
-j1 6.589 0.966 -5.62s (6.82x)
-j10 1.404 0.659 -0.74s (2.13x)
ext/ldap (none) 6.641 2.920 -3.72s (2.27x)
-j1 6.641 2.937 -3.70s (2.26x)
-j10 7.438 2.999 -4.44s (2.48x)
ext/libxml (none) 2.498 0.660 -1.84s (3.78x)
-j1 2.498 0.669 -1.83s (3.73x)
-j10 0.941 0.674 -0.27s (1.40x)
ext/mbstring (none) 30.374 2.481 -27.89s (12.24x)
-j1 30.374 10.504 -19.87s (2.89x)
-j10 7.703 2.473 -5.23s (3.11x)
ext/mysqli (none) 10.021 3.824 -6.20s (2.62x)
-j1 10.021 3.684 -6.34s (2.72x)
-j10 9.859 3.834 -6.03s (2.57x)
ext/odbc (none) 0.904 0.452 -0.45s (2.00x)
-j1 0.904 0.352 -0.55s (2.57x)
-j10 1.050 0.585 -0.47s (1.79x)
ext/opcache (none) 67.487 8.283 -59.20s (8.15x)
-j1 67.487 54.224 -13.26s (1.24x)
-j10 13.405 8.552 -4.85s (1.57x)
ext/openssl (none) 45.311 5.035 -40.28s (9.00x)
-j1 45.311 28.753 -16.56s (1.58x)
-j10 49.155 5.106 -44.05s (9.63x)
ext/pcntl (none) 6.540 2.506 -4.03s (2.61x)
-j1 6.540 2.888 -3.65s (2.26x)
-j10 3.305 2.554 -0.75s (1.29x)
ext/pcre (none) 12.600 1.716 -10.88s (7.34x)
-j1 12.600 3.450 -9.15s (3.65x)
-j10 4.478 1.811 -2.67s (2.47x)
ext/pdo (none) 1.933 0.628 -1.31s (3.08x)
-j1 1.933 0.549 -1.38s (3.52x)
-j10 0.971 0.679 -0.29s (1.43x)
ext/pdo_dblib (none) 237.447 8.270 -229.18s (28.71x)
-j1 237.447 11.324 -226.12s (20.97x)
-j10 200.863 8.234 -192.63s (24.39x)
ext/pdo_firebird (none) 0.124 0.249 +0.12s (0.50x)
-j1 0.124 0.131 +0.01s (0.95x)
-j10 0.289 0.269 -0.02s (1.07x)
ext/pdo_mysql (none) 3.330 2.324 -1.01s (1.43x)
-j1 3.330 2.451 -0.88s (1.36x)
-j10 3.807 2.287 -1.52s (1.66x)
ext/pdo_odbc (none) 2.126 2.233 +0.11s (0.95x)
-j1 2.126 2.120 -0.01s (1.00x)
-j10 2.467 2.160 -0.31s (1.14x)
ext/pdo_pgsql (none) 3.288 2.311 -0.98s (1.42x)
-j1 3.288 2.564 -0.72s (1.28x)
-j10 2.582 2.275 -0.31s (1.13x)
ext/pdo_sqlite (none) 20.236 14.875 -5.36s (1.36x)
-j1 20.236 15.297 -4.94s (1.32x)
-j10 16.838 14.561 -2.28s (1.16x)
ext/pgsql (none) 1.654 0.751 -0.90s (2.20x)
-j1 1.654 0.626 -1.03s (2.64x)
-j10 2.032 2.158 +0.13s (0.94x)
ext/phar (none) 56.768 5.441 -51.33s (10.43x)
-j1 56.768 24.778 -31.99s (2.29x)
-j10 11.601 7.420 -4.18s (1.56x)
ext/posix (none) 4.892 0.658 -4.23s (7.43x)
-j1 4.892 0.796 -4.10s (6.15x)
-j10 1.686 0.822 -0.86s (2.05x)
ext/random (none) 4.283 0.492 -3.79s (8.71x)
-j1 4.283 0.774 -3.51s (5.53x)
-j10 0.964 0.445 -0.52s (2.17x)
ext/readline (none) 2.292 0.676 -1.62s (3.39x)
-j1 2.292 1.228 -1.06s (1.87x)
-j10 1.065 0.640 -0.42s (1.66x)
ext/reflection (none) 31.491 1.242 -30.25s (25.36x)
-j1 31.491 4.245 -27.25s (7.42x)
-j10 6.614 1.262 -5.35s (5.24x)
ext/session (none) 17.882 2.428 -15.45s (7.36x)
-j1 17.882 10.166 -7.72s (1.76x)
-j10 3.771 2.731 -1.04s (1.38x)
ext/shmop (none) 0.474 0.331 -0.14s (1.43x)
-j1 0.474 0.307 -0.17s (1.54x)
-j10 0.336 0.341 +0.01s (0.99x)
ext/simplexml (none) 9.555 0.720 -8.83s (13.27x)
-j1 9.555 1.109 -8.45s (8.62x)
-j10 2.105 0.795 -1.31s (2.65x)
ext/skeleton (none) 0.117 0.196 +0.08s (0.60x)
-j1 0.117 0.126 +0.01s (0.93x)
-j10 0.194 0.193 -0.00s (1.01x)
ext/snmp (none) 43.267 6.695 -36.57s (6.46x)
-j1 43.267 6.455 -36.81s (6.70x)
-j10 43.186 6.596 -36.59s (6.55x)
ext/soap (none) 40.262 2.767 -37.49s (14.55x)
-j1 40.262 10.148 -30.11s (3.97x)
-j10 7.781 2.916 -4.87s (2.67x)
ext/sockets (none) 11.027 2.600 -8.43s (4.24x)
-j1 11.027 3.256 -7.77s (3.39x)
-j10 3.956 2.757 -1.20s (1.43x)
ext/sodium (none) 12.679 1.117 -11.56s (11.35x)
-j1 12.679 2.257 -10.42s (5.62x)
-j10 10.710 1.115 -9.60s (9.61x)
ext/spl (none) 51.527 2.119 -49.41s (24.32x)
-j1 51.527 9.217 -42.31s (5.59x)
-j10 9.515 2.597 -6.92s (3.66x)
ext/sqlite3 (none) 6.761 0.737 -6.02s (9.17x)
-j1 6.761 1.203 -5.56s (5.62x)
-j10 1.494 0.707 -0.79s (2.11x)
ext/standard (none) 322.044 14.536 -307.51s (22.15x)
-j1 322.044 73.297 -248.75s (4.39x)
-j10 56.019 14.828 -41.19s (3.78x)
ext/sysvmsg (none) 0.541 0.378 -0.16s (1.43x)
-j1 0.541 0.211 -0.33s (2.56x)
-j10 0.374 0.375 +0.00s (1.00x)
ext/sysvsem (none) 0.300 0.304 +0.00s (0.99x)
-j1 0.300 0.242 -0.06s (1.24x)
-j10 0.370 0.310 -0.06s (1.19x)
ext/sysvshm (none) 0.997 0.471 -0.53s (2.12x)
-j1 0.997 0.380 -0.62s (2.62x)
-j10 0.638 0.582 -0.06s (1.10x)
ext/tidy (none) 3.212 0.586 -2.63s (5.48x)
-j1 3.212 0.706 -2.51s (4.55x)
-j10 1.058 0.783 -0.28s (1.35x)
ext/tokenizer (none) 3.469 0.565 -2.90s (6.14x)
-j1 3.469 0.693 -2.78s (5.01x)
-j10 0.999 0.552 -0.45s (1.81x)
ext/uri (none) 22.419 0.779 -21.64s (28.78x)
-j1 22.419 2.107 -20.31s (10.64x)
-j10 5.517 0.730 -4.79s (7.56x)
ext/xml (none) 4.333 0.764 -3.57s (5.67x)
-j1 4.333 0.809 -3.52s (5.36x)
-j10 1.224 0.779 -0.44s (1.57x)
ext/xmlreader (none) 4.449 0.618 -3.83s (7.20x)
-j1 4.449 0.697 -3.75s (6.38x)
-j10 1.379 0.827 -0.55s (1.67x)
ext/xmlwriter (none) 3.234 0.618 -2.62s (5.23x)
-j1 3.234 0.592 -2.64s (5.46x)
-j10 0.925 0.553 -0.37s (1.67x)
ext/xsl (none) 0.129 0.416 +0.29s (0.31x)
-j1 0.129 0.135 +0.01s (0.96x)
-j10 0.340 0.342 +0.00s (0.99x)
ext/zend_test (none) 10.192 1.549 -8.64s (6.58x)
-j1 10.192 5.229 -4.96s (1.95x)
-j10 2.176 1.376 -0.80s (1.58x)
ext/zip (none) 11.918 1.040 -10.88s (11.46x)
-j1 11.918 1.468 -10.45s (8.12x)
-j10 2.609 0.904 -1.71s (2.89x)
ext/zlib (none) 11.232 1.199 -10.03s (9.37x)
-j1 11.232 2.992 -8.24s (3.75x)
-j10 2.196 1.025 -1.17s (2.14x)
sapi/cgi (none) 5.833 2.246 -3.59s (2.60x)
-j1 5.833 5.047 -0.79s (1.16x)
-j10 2.242 2.142 -0.10s (1.05x)
sapi/cli (none) 24.166 2.849 -21.32s (8.48x)
-j1 24.166 16.504 -7.66s (1.46x)
-j10 5.508 2.902 -2.61s (1.90x)
sapi/fpm (none) 1.595 0.421 -1.17s (3.79x)
-j1 1.595 0.417 -1.18s (3.82x)
-j10 1.491 0.410 -1.08s (3.64x)
sapi/phpdbg (none) n/a n/a n/a
-j1 246.564 6.091 -240.47s (40.48x)
-j10 n/a n/a n/a
tests (none) 64.478 4.645 -59.83s (13.88x)
-j1 64.478 20.204 -44.27s (3.19x)
-j10 13.876 4.398 -9.48s (3.16x)
Zend (none) 373.757 15.621 -358.14s (23.93x)
-j1 373.757 49.758 -324.00s (7.51x)
-j10 75.593 13.010 -62.58s (5.81x)

The -j1 row compares the master default with the explicit sequential opt-out.

Misc

  • At first, I found it weird that it would propose adding test-only code to the SAPI. But then I learned that the already
    existing, internal --repeat flag does exactly that.
  • I am pretty sure that there are reasons for Windows in CI only running on -j2, and parallel not being the default. I, however, could not find out what these reasons are; hence, I kept changes Codex proposed.
  • not sure whether we fancy adding new phpt sections like MAX_CONCURRENCY, but it shows gains.
  • buggy concurrent execution CLI output (eg: TEST 22386/22094 [2/10 concurrent test workers running]) was fixed.
  • Codex did not consider this done yet; so if there is general interest I will let it finish the job.
  • I know, another huge PR by me. Keen to split that up as we go.

@NickSdot

Copy link
Copy Markdown
Contributor Author

Didn't expect this to be smooth in the very first CI run; will look into these.

@arnaud-lb

arnaud-lb commented Jul 29, 2026

Copy link
Copy Markdown
Member

Interesting work!

Related: #20980

Please submit each commit as a separate PR to make reviewing easier (maybe not all at once to avoid spamming the repos. Start with the most impacting ones). I will review at least some of them.

@NickSdot

Copy link
Copy Markdown
Contributor Author

Interesting work!

Related: #20980

Please submit each commit as a separate PR to make reviewing easier (maybe not all at once to avoid spamming the repos. Start with the most impacting ones). I will review at least some of them.

Thanks, Arnaud! Would you, for now, be fine with reviewing the individual commit diffs here? Like, so that we can pre-decide a few things here first, and based on that I am happily splitting up into individual PRs. The commits table in the PR desc brings you straight to the same diff you would have in a dedicated PR.

#20980 is indeed (admittedly naively) solved by this change. By decreasing batch size, much longer running batches get automatically more rare. Though, this is something that surely could be further improved in the future.

@NickSdot

Copy link
Copy Markdown
Contributor Author

Please submit each commit as a separate PR to make reviewing easier (maybe not all at once to avoid spamming the repos. Start with the most impacting ones). I will review at least some of them.

@arnaud-lb I stand corrected here. Didn't expect you commenting directly on the commits but here in the changes; my force pushes to keep the related changes all in one commit break the relation to the comments in that case. I already cherry picked #22939 and #22934. Will will follow up with more.

@NickSdot

Copy link
Copy Markdown
Contributor Author

@arnaud-lb

I will keep answering here for meta things not related to only one specific PR.

  1. The split into 10 logical PRs, plus one more fix, is done -- they are all linked in the description here. Didn't mark them all as ready for review yet and would appreciate if you could take a high level look at how things were split.
  2. I feel like I missed to answer one of your comments somewhere; if that's the case please hint me at it.
  3. Please let me know if any of the PRs need entries in NEWS, UPGRADING.INTERNALS or the like.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants