szepeviktor/phpstan-wordpress

Performance issues

herndlm opened this issue ยท 23 comments

I noticed that this extension increases both CPU and memory resources quite a bit. Sorry, I did not have time to profile it, but the "problems" seem to be there since 1.0.0 at least already.

I have quite a small plugin that uses the latest phpstan, phpstan-webmozart-assert extension and phpstan-wordpress extension.

Test case running vendor/bin/phpstan --debug --verbose analyse
With this extension: ~17s using ~310M memory
Without this extension:~4s using ~110M memory

Could be something really simple that we need to improve here, who knows. I can profile it soon hopefully. I see slowness in one specific file, so I'll need to inspect that one in particular.

I've noticed this during the tests and I think it might be the HookDocsRule, I haven't looked into it though

I thought about that one as well, but assumed it was added very recently (after 1.0.0). I noticed that all versions since 1.0.0 (at least, I did not go back further) are more or less affected. Previous ones are just a tiny bit faster. Must be something older I assume or related to changes in phpstan itself.

I can speculate for free! Loading/parsing WP core stubs is 90% of your problem.

OK, I took a beer home with me from the office fridge I'd like to drink. I'll promise I'll only do that if I either fix this or at least know what exactly is causing it. Wish me luck :)

Try commenting out this line and see seconds and megabytes!

- %rootDir%/../../php-stubs/wordpress-stubs/wordpress-stubs.php

(there will be 1 zillion errors)

No Viktor, you're ruining it! But sounds like we can't fix that :/

yep, you're absolute right. this "stub file" which is used as bootstrap file is a bit over 4M big and the reason for that apparently. It looks also like it got worse with the latest phpstan versions, e.g. if I go back I can see that almost every minor increased resources a bit :/
1.0.0: 10s/225M, 1.6.0: 14s/305M
1.0.0 (without stubs): 2s/70M,1.6.0 (without stubs): 3s/85M

maybe, still, something can be improved in phpstan here. we might find out via profiling, but I'm too tired today, another time. would also be interesting if those stubs can somehow be made slimmer

Interesting, I started stripping things out of that huge stub file. Because I saw a couple of namespace{} blocks, I moved them into dedicated files. I ended up with 4 "smaller" files, configured them in my config and boom, 1.6.0 resources are now 9s/215M (compared to 14s/305M). so splitting this stuff into multiple smaller files could be a workaround at least.

UPDATE: did a bit more splitting, so that each file stays under 1M at least, then I got 7s/180M. looks like the benefits are getting smaller, but the files are still huuge. maybe there's a good way of splitting them. keeping that one single-point-of-entry stub and requiring other smaller stubs from there seems to be working too. that could be a solution to look into.

fyi @ondrejmirtes, summary: apparently huge stub/bootstrap files make analysis slow and hog memory. splitting them into smaller files seems to be fine or better at least. does it make sense to look into phpstan itself improving handling of such big (~ 4M) stub/bootstrap files?

and another thing that seems to be improving the situation extremely is instead of specifying the huge stubs file as bootstrap file, directly specifying a wordpress-checkout via scanDirectories. That was quite simple and gave me good results too (7s/140M). I assume it also benefits more from caching. but how can this be done user friendly for people using this extension ๐Ÿค”

instead of specifying the huge stubs file as bootstrap file, directly specifying a wordpress-checkout via scanDirectories

๐Ÿ˜ฟ ๐Ÿ˜ฟ ๐Ÿ˜ฟ

This makes autoloading extended classes impossible.

class MyClass extends A_WP_Core_Class

... yes, PHPStan uses Composer's autoloader.

apparently huge stub/bootstrap files make analysis slow and hog memory

can you try it with bleedingEdge enabled? And can you find the actual reason why is it slower / takes more memory with php-meminfo? ๐Ÿ˜Š

This makes autoloading extended classes impossible.

does it really? I thought that option was specifically there for such cases, e.g. https://phpstan.org/user-guide/discovering-symbols#third-party-code-outside-of-composer-dependencies
but if you can get me some example or describe that case further I'm happy to test :)

can you try it with bleedingEdge enabled? And can you find the actual reason why is it slower / takes more memory with php-meminfo? ๐Ÿ˜Š

ok, got some new numbers. Unfortunately I'm not really seeing a difference with bleedingEdge

phpstan 1.6.3

extension disabled: 3s/104M
scanDirectories: 6s/144M
bootstrap stub: 16s/311M
bootstrap stub + bleeding edge: 15-16s/311M (maybe just a bit faster, basically the same)

just to rule that out too: directly executing the stub file via php takes ~0.1s 

Sure, I can try to profile it further, but this might be not easy and take a while. Don't expect results immediately, but I'll try to look into it today/tomorrow :)

Can you please publish the project so I can test it myself and see if I can rule out the cause?

Not the one I currently test with unfortunately, but it's easy to reproduce by basically just using this extension. I prepared something at https://github.com/herndlm/phpstan-wordpress-stubs-perf-repro

Another aspect is to reduce the size of the wordpress-stubs.php file.

I wonder whether PHPStan needs to know private/protected method signatures. The WordPress Stubs are generated via the PHP Stubs Generator, which includes all methods from a class, not just the public ones.

Keeping only the public methods will reduce the file by around 10 per cent. I am happy to help change the PHP Stubs Generator behaviour.

This makes autoloading extended classes impossible.

does it really? I thought that option was specifically there for such cases, e.g. phpstan.org/user-guide/discovering-symbols#third-party-code-outside-of-composer-dependencies but if you can get me some example or describe that case further I'm happy to test :)

I do not have records or an actual example. I have also tried scanFiles: but it failed. Maybe it was a WP-CLI command that needs to extend WPCLI_Command.

Simply I don't remember.

This is the profile with the extension included: https://blackfire.io/profiles/8e044619-2f50-474f-87a9-dc26b5ba230c/graph?settings%5Bdimension%5D=wt&settings%5Bdisplay%5D=landscape&settings%5BtabPane%5D=nodes&selected=in_array&callname=main()&constraintDoc=

The slowest thing is PHPDoc parsing. Because everything is in a single file, PHPStan has to go through all PHPDocs present there. Why splitting into smaller files helps - not all of them have to be searched for a requested class/function.

I think the problem can be solved by making FileTypeMapper a bit more lazy (https://github.com/phpstan/phpstan-src/blob/1.7.x/src/Type/FileTypeMapper.php). When you request a PHPDoc, it's already lazy, but for two reasons PHPDocs are parsed beforehand:

Because both of those are part of NameScope objects where the creation is not lazy.

I'm not saying it's going to be easy :)

I'm not saying it's going to be easy :)

Thank you for your work in advance!

Why splitting into smaller files helps

BTW Everyone is free to maintain a fork of WP core stubs in separate files!!

And another workaround is still, even without any changes of this extension, to specify a WordPress directory via scanDirectories, e.g. https://github.com/johnpbloch/wordpress-core via vendor if that package is installed as dev dep (which also helps phpstorm).

FYI scanDirectories can work only with bleedingEdge enabled - if you extend/implement some of those classes.

reported at phpstan as phpstan/phpstan#8349 so that we don't forget