Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve ReflectionEnum->getCases() performance #1410

Open
wants to merge 8 commits into
base: 6.30.x
Choose a base branch
from

Conversation

staabm
Copy link
Contributor

@staabm staabm commented Mar 22, 2024

the method is showing up in profiles of phpstan/phpstan#10772
I am aware that this PR will not fix the root cause of the perf problem, but its a low hanging fruit on my way :)

grafik

lets remove the array_map magic and the unnecessary closure invocation

@kukulich
Copy link
Collaborator

@staabm yes, the best optimization would be to remove the adapter layer from PHPStan :)

@Ocramius
Copy link
Member

I'm OK with merging this, but it will eventually be re-factored again to an array_map() later on anyway :|

I'm convinced method tracing is actually inflating this more than it actually is relevant.

I'd rather memoize cases instead, in future.

@Ocramius
Copy link
Member

lets remove the array_map() magic and the unnecessary closure invocation

FWIW, array_map() is less magic than loops in general: it's just that the engine suuuuucks at handling it :-P

$cases = $this->betterReflectionEnum->getCases();

$mappedCases = [];
foreach ($cases as $case) {
if ($this->betterReflectionEnum->isBacked()) {
Copy link
Member

@Ocramius Ocramius Mar 22, 2024

Choose a reason for hiding this comment

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

This method call is repeated in the loop: let's pull it out of here

@staabm
Copy link
Contributor Author

staabm commented Mar 22, 2024

FWIW, array_map() is less magic than loops in general

I feel array_map makes me read code backwards. I really like the simplicity of foreach. maybe I suck in reading it as much as the engine ;-)

@Ocramius
Copy link
Member

You need to spend a couple months writing FP (Rust, Haskell, clojure, etc.), and then C-style loops start looking like the garbled mess that they are ;-)

@@ -530,14 +530,18 @@ public function getCase(string $name): ReflectionEnumUnitCase|ReflectionEnumBack
/** @return list<ReflectionEnumUnitCase|ReflectionEnumBackedCase> */
Copy link
Member

Choose a reason for hiding this comment

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

Consideration here: we perhaps return list<ReflectionEnumUnitCase>|list<ReflectionEnumBackedCase> rather than list<ReflectionEnumUnitCase|ReflectionEnumBackedCase> 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

AFAIK phpstan is not able to differentiate it, but I can do it if you prefer

Copy link
Member

Choose a reason for hiding this comment

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

It's more precise for the consumer regardless, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah. I already changed it silently :)

@staabm
Copy link
Contributor Author

staabm commented Mar 22, 2024

I'd rather memoize cases instead, in future.

done

@staabm
Copy link
Contributor Author

staabm commented Mar 22, 2024

please advice on how to make psalm happy or how/where to move this thing ;)

@Ocramius
Copy link
Member

I can try helping later tonight, but currently stuck with another issue at work.

@ondrejmirtes
Copy link
Contributor

My opinion - this kind of micro optimization isn't worth it. It's only showing 8 % improvement in PHPStan because it's working with huge amounts of data. Once the root cause in PHPStan is fixed then the improvement isn't going to be 8 %, but barely measurable.

The root cause was already fixed: phpstan/phpstan-src#2985

@Ocramius
Copy link
Member

Most likely, but spiking memoization as a general outcome is still worth pursuing :D

@staabm
Copy link
Contributor Author

staabm commented Mar 22, 2024

I am fine with closing it


namespace Roave\BetterReflection\Util;

/** @template T */
Copy link
Member

Choose a reason for hiding this comment

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

I'm thinking of this, meanwhile:

/** 
 * @template T
 * @internal do not touch: you have been warned.
 */
final class Memoize
{
    private readonly mixed $cached;
    /** @param pure-Closure(): T $cb */
    public function __construct(private Closure|null $cb) {}
    public function get(): mixed {
        if ($this->cb) {
            $this->cached = ($this->cb)();
            $this->cb = null;
       }
        
        return $this->cached;
    }
}

This could be used general-purpose on most adapters, if needed.

Copy link
Member

Choose a reason for hiding this comment

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

mostly what you wrote, except I don't care if $cached is null (valid result)

Copy link
Contributor Author

@staabm staabm Mar 22, 2024

Choose a reason for hiding this comment

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

thanks - copied it over. the SA tools don't like it ;)

Copy link
Member

Choose a reason for hiding this comment

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

Well, that's good :D

We are indeed bending the rules here: hence the @internal stuff as well.

I'll likely need to take over on this: a good measure of error suppression will be needed, but the impure-closure bit is most annoying (it shouldn't consider it impure 🤔 )

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.

4 participants