Skip to content

Disable PCOV if installed (40% faster on affected systems) - #6253

Closed
AJenbo wants to merge 1 commit into
phpstan:2.2.xfrom
AJenbo:pcov
Closed

AJenbo wants to merge 1 commit into
phpstan:2.2.xfrom
AJenbo:pcov

Conversation

@AJenbo

@AJenbo AJenbo commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Very 2017 Déjà vu, this often affect CI where PCOV may be installed for faster coverage then xdebug, but when coverage isn't needed it can still hurt performance quite a bit (xdebug was 400% if I remember correctly).

Unlike xdebugHandler this isn't ported from another package or spun out from one. If you prefer I could create ajenbo/pcov-handler and then start shopping it around other projects as well. But even for that case maybe it would be good to start with it as part of phpstan and then later spin it off from there?

@staabm

staabm commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Could you show a proof in a reproducer repo how much pcov affects a github actions CI build?

@AJenbo

AJenbo commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

Scout honer and screen shots is the best I can do:

PHP 8.4 with PCOV:
image

PHP 8.5 with PCOV:
image

PHP 8.4 without PCOV:
image

PHP 8.5 without PCOV:
image

So 35-45% on GitHub action as well so the effect is probably pretty flat. The original measure was on PHPStan it self from my workstation, this is a LARGE Laravel lib (Larastan + phpstan-phpunit + phpstan-mockery + phpstan-strict-rules, no Bladestan since it's a lib with no templates)

@staabm

staabm commented Aug 26, 2026 •

Copy link
Copy Markdown
Contributor

Nice.

I agree that we should reflect composer/xdebug-handler 1:1 in handling this (regarding api and process restarts etc)

Might be even worth open a issue on composer and see how they think about doing similar things for composer (and measure impact of pcov on composer itself)

$errorOutput->writeLineFormatted('<comment>Tip: The pcov extension is active, which makes this run slower than it needs to be.</comment>');
$errorOutput->writeLineFormatted('pcov hooks into every function call in the process, even though PHPStan never collects code coverage.');
$errorOutput->writeLineFormatted(sprintf('PHPStan disables it in its worker processes - run PHPStan with "php -d %s" to disable it in the main process too.', PcovHelper::DISABLED_INI_SETTING));
$errorOutput->writeLineFormatted(sprintf('Set %s=1 if you need pcov to stay enabled.', PcovHelper::ALLOW_ENV_VARIABLE));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do you think there's a valid use case? If not you can remove the env var support.

Also how different are the runs with pcov off, and with pcov on but PHPStan restarting? I'm pondering not printing any message at all, the output is really noisy already.

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.

No not really, it was mostly to mirror the other on in case it got split out, and I wasn't sure how it would look in PHPStan's own CI but that turned out to be a non issue.

@staabm

staabm commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

We should assert we don't need 2 restarts per process when xdebug and pcov are enabled?

@ondrejmirtes

Copy link
Copy Markdown
Member

Oh yeah, no idea whether we can hook onto XdebugHandler like that.

Also there's yet another restart in bin/phpstan now - for turbo-ext, under certain circumstances. It's done with pcntl_exec which might be nicer than proc_open (if available) but not sure.

@ondrejmirtes

Copy link
Copy Markdown
Member

Maybe we could build our own system - find reasons for restart and if count($reasons) > 0 then compose the right CLI command and do it.

@staabm

staabm commented Aug 26, 2026 •

Copy link
Copy Markdown
Contributor

This issue makes me think that other profiler extensions like blackfire, php-spx might also have the same problem

We should measure that

@AJenbo

AJenbo commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

I just found one issue, the new fork work means we have to restart the main process or the workers will inherit the pcov state.

This issue makes me think that other profiler extensions like blackfire, php-spx might also have the same problem

Good call, maybe the realization here is that we should build a PHPStan dedicated helper for all of these, detect them all, disable them all in one restart, update when new ones are found. xdebug can get special handeling inside of it for allowing debugging.

@ondrejmirtes

Copy link
Copy Markdown
Member

And --debug should never restart. I actually sometimes need to run with SPX/Blackfire 😂 And I don't think we should pollute the options with --pcov, --blackfire, --spx etc.

@AJenbo

AJenbo commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

This issue makes me think that other profiler extensions like blackfire, php-spx might also have the same problem

I have something working, I'll go over the various extensions to see how much they each affect performance.

@AJenbo

AJenbo commented Aug 27, 2026 •

Copy link
Copy Markdown
Contributor Author

3 extensions turned out to cause issues, the rest is within noise level:

image

Most of them can simply be disabled and don't actually need to be unloaded which allows us to still reflect on the methods they add.

@staabm

staabm commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

today while looking into phpstan consuming repos, I stumbled over kimai which was suprisingly slow in CI:
https://github.com/kimai/kimai/actions/runs/34611469594/job/103302755736

it takes 45 seconds in CI, while it takes 5 seconds on my local machine. I guess its also related to pcov


@AJenbo please rebase and target 2.3.x

@SanderMuller please review

@SanderMuller

Copy link
Copy Markdown
Contributor

today while looking into phpstan consuming repos, I stumbled over kimai which was suprisingly slow in CI: https://github.com/kimai/kimai/actions/runs/34611469594/job/103302755736

it takes 45 seconds in CI, while it takes 5 seconds on my local machine. I guess its also related to pcov

@AJenbo please rebase and target 2.3.x

@SanderMuller please review

Did you test on PHP 8.2? wonder if PHP 8.5 itself is also faster. And Kimai uses the standard ubuntu runner I think, which has only a couple cores. Curious what part of 45s vs 5s is due to which factors (Amount of cores / PHP version / pcov)

@AJenbo

AJenbo commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

@staabm I'm working on a bit of a rewrite where it handle all 4 extension and jit settings to avoid repeated reboots and consolidating all of that logic and make it simple to add any future extensions that needs managment, I was unable to make progress on it over the past two weeks because of some things at work, but I expect to finish it this week. Thanks for the patience.

@staabm staabm mentioned this pull request Sep 13, 2026
2 of 6 tasks
@staabm

staabm commented Sep 13, 2026 •

Copy link
Copy Markdown
Contributor

Did you test on PHP 8.2? wonder if PHP 8.5 itself is also faster. And Kimai uses the standard ubuntu runner I think, which has only a couple cores. Curious what part of 45s vs 5s is due to which factors (Amount of cores / PHP version / pcov)

@SanderMuller

the repo uses 2 phpstan jobs:

pcov loaded: 34s + 32s = 66s (PHP 8.2)
pcov removed - no result cache: 27s + 26s = 53s seconds without "Turbo" (PHP 8.2)
with cold result cache and PHPStan Turbo: 17s+17s (PHP 8.3)
with warm result cache and PHPStan Turbo: 1,4s+1,4s (PHP 8.3)

@SanderMuller

Copy link
Copy Markdown
Contributor

I measured the premise rather than the diff, since a rewrite is coming. The effect is real and a bit larger than the title says.

4524-file project, cold, released 2.2.14 phar, PHP 8.5.8, pcov 1.0.12 built locally, 3 interleaved rounds, medians. I asserted the pcov state in all 15 processes with a bootstrapFiles probe. My first attempt used a -d flag on the parent, which never reaches the workers, and the whole effect read as zero.

wall CPU
pcov not installed 12.82s 83.0s
pcov loaded, enabled 19.29s 123.5s
pcov loaded, pcov.enabled=0 13.77s 80.8s

So pcov costs +50% wall and +49% CPU, and turning it off gets all of it back.

@ondrejmirtes, that also answers your question on AnalyseCommand.php:736. Disabled is indistinguishable from absent: 80.8s against 83.0s CPU, with the disabled arm marginally lower, which is noise. Nothing has to be unloaded.

Three constraints that decide the shape, all checked here:

pcov.enabled is PHP_INI_PERDIR. ini_set() returns false and the value does not move, so the only ways in are -d and php.ini.

Workers are forked on this machine, on the phar and on a source checkout alike. pcntl_fork() copies the parent's pcov state, so spelling -d pcov.enabled=0 into the worker command line does nothing. I tried exactly that patch in ProcessHelper and all 15 processes still reported pcov.enabled=1. Only the parent's state counts, which is what @AJenbo said in August.

The phar already restarts itself unconditionally to load the turbo extension, and it does so under --debug too. On that path pcov.enabled=0 can ride the restart that already happens, at no extra cost. A source install does not restart today, so that one would gain a restart it does not have.

What I did not check: any of this on Linux. kimai's CI is where the report came from. Fork needs pcntl and a turbo binary for that platform, so the restart picture there may differ.

@AJenbo

AJenbo commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

What I did not check: any of this on Linux.

Good news, I'm on Linux.

@staabm

staabm commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

just adding a side-note: might be worth to also restart for the tideways extension, as it seems to trigger segfaults with turbo: phpstan/phpstan#15407

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.

4 participants