Skip to content

[12.x] perf: Optimize BladeCompiler #55703

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

Merged
merged 3 commits into from
May 10, 2025
Merged

[12.x] perf: Optimize BladeCompiler #55703

merged 3 commits into from
May 10, 2025

Conversation

rzv-me
Copy link
Contributor

@rzv-me rzv-me commented May 10, 2025

This PR reduces the execution time for getOpenAndClosingPhpTokens() by up to 5-8x and the compile() by up to 3x, based on my testing.

As the improvements are for the BladeCompiler, these benefit the most situations when the Blade Components are not cached (local and misconfigured production environments).

I used the Livewire starter kit on PHP 8.3 and my mac with M3 Max to test my changes.

page v1 v2 v3 this version (v4)
complex page (92 calls) 43.06 ms 16.73 ms 13.98 ms 8.65 ms
billing page (51 calls) 18.74 ms 7.22 ms 6.22 ms 4.18 ms
dashboard (94 calls) 35.42 ms 13.69 ms 11.60 ms 7.57 ms
profile page (56 calls) 23.21 ms 8.97 ms 7.64 ms 5.05 ms

I went through a few iterations which I will add here:
v1: Original

        return (new Collection(token_get_all($contents)))
            ->pluck(0)
            ->filter(function ($token) {
                return in_array($token, [T_OPEN_TAG, T_OPEN_TAG_WITH_ECHO, T_CLOSE_TAG]);
            });

v2: Most obvious optimization, doing the pluck after the filter

        return (new Collection(token_get_all($contents)))
            ->filter(function ($token) {
                return in_array($token, [T_OPEN_TAG, T_OPEN_TAG_WITH_ECHO, T_CLOSE_TAG]);
            })
            ->pluck(0);

v3: Switching to foreach(), instead of using collection

        $tokens = [];
        foreach (token_get_all($contents) as $token) {
            if (in_array($token, [T_OPEN_TAG, T_OPEN_TAG_WITH_ECHO, T_CLOSE_TAG])) {
                $tokens[] = $token[0];
            }
        }
        return new Collection($tokens);

v4: Final optimization: Switch from in_array, to multiple if conditions

        $tokens = [];
        foreach (token_get_all($contents) as $token) {
            if ($token[0] === T_OPEN_TAG || $token[0] === T_OPEN_TAG_WITH_ECHO || $token[0] === T_CLOSE_TAG) {
                $tokens[] = $token[0];
            }
        }
        return new Collection($tokens);

@taylorotwell taylorotwell merged commit ea17178 into laravel:12.x May 10, 2025
58 checks passed
@browner12
Copy link
Contributor

is the bulk of the benefit from removing the in_array call that was running N times?

I'm curious if you would have got the same improvements sticking with the chained Collection calls, but removing the in_array.

@calebdw
Copy link
Contributor

calebdw commented May 13, 2025

You might could get some additional performance gains by using a variable instead of repeatedly accessing the array:

        foreach (token_get_all($contents) as $token) {
            $token = $token[0];
            if ($token === T_OPEN_TAG || $token === T_OPEN_TAG_WITH_ECHO || $token === T_CLOSE_TAG) {
                $tokens[] = $token;
            }
        }

Alternatively, you might could use array_column to avoid the array access altogether:

        foreach (array_column(token_get_all($contents), 0) as $token) {
            if ($token === T_OPEN_TAG || $token === T_OPEN_TAG_WITH_ECHO || $token === T_CLOSE_TAG) {
                $tokens[] = $token;
            }
        }

@browner12
Copy link
Contributor

been programming PHP for 20 years and TIL array_column exists 🤯

@rzv-me
Copy link
Contributor Author

rzv-me commented May 13, 2025

@browner12 the bulk of the imprvements come from 2 places:

  • not doing the pluck before filtering, even better when you filter and pluck(0) at the same time
  • not doing the in_array() when filtering, as the options are static and very limited (3)

@calebdw

  • the first suggestion might be a bit better, but it also means there is an extra memory allocation
  • as for the second suggestion, I suspect it might be the same as, doing the pluck(0) before the filter
    did you benchmark them?

@calebdw
Copy link
Contributor

calebdw commented May 13, 2025

I didn't benchmark anything, just thinking out loud---performance is one of those things that usually defies intuition

@shaedrich
Copy link
Contributor

        foreach (token_get_all($contents) as $token) {
            $token = $token[0];
            if ($token === T_OPEN_TAG || $token === T_OPEN_TAG_WITH_ECHO || $token === T_CLOSE_TAG) {
                $tokens[] = $token;
            }
        }

You can even avoid using indices altogether by using array destructuring:

        foreach (token_get_all($contents) as $token) {
-           $token = $token[0];
+           [$token] = $token;
            if ($token === T_OPEN_TAG || $token === T_OPEN_TAG_WITH_ECHO || $token === T_CLOSE_TAG) {
                $tokens[] = $token;
            }
        }

@donnysim
Copy link
Contributor

You can also do

foreach (token_get_all($contents) as [$token]) {
```

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.

6 participants