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

Performance improvement: Reduce the use of the _wp_array_get function #51116

Merged
merged 33 commits into from
Sep 19, 2023

Conversation

aristath
Copy link
Member

@aristath aristath commented May 31, 2023

What?

_wp_array_get() is an expensive function, and it's called thousands of times on each page-view on the frontend. In https://core.trac.wordpress.org/ticket/58376 we improved slightly the performance of that function, but the fact remains that it's called more times than it should.

Why?

Because performance & sustainability.

How?

Inspiration came from a discussion with @jrfnl

In many cases, we can replace _wp_array_get() with a much simpler and faster isset() check.
The isset() function is capable of checking nested arrays, so isset( $foo['a']['b']['c'] ) will return false even if $foo['a'] is unset, without throwing any errors/warnings etc.
When _wp_array_get() cannot be directly replaced with isset(), it would be good practice to wrap it in an isset function so _wp_array_get will only run when it needs to.

In order to simplify the syntax, instead of using isset() checks, we can use the Null coalescing operator, which further simplifies the whole implementation.
So we can replace for example this line:

$has_font_family_support = _wp_array_get( $typography_supports, array( '__experimentalFontFamily' ), false );

with this:

$has_font_family_support = $typography_supports['__experimentalFontFamily'] ?? false;

which would be the equivalent of this:

$has_font_family_support = isset( $typography_supports['__experimentalFontFamily'] )
    ? $typography_supports['__experimentalFontFamily']
    : false;

The resulting code is shorter, easier to understand, and faster.

Of course the above modifications can only work when we know the path we're looking for. If we don't, then _wp_array_get should still be used.

@github-actions
Copy link

github-actions bot commented May 31, 2023

Flaky tests detected in 48efd0aa7133b7273e663700030a09f67cb211b4.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/6168673762
📝 Reported issues:

@aristath aristath force-pushed the try/reduce-wp_array_get-use branch from 7f16c3b to 3718318 Compare May 31, 2023 10:04
@aristath aristath changed the title 🚧 🚧 🚧 Reduce the use of the _wp_array_get function Reduce the use of the _wp_array_get function May 31, 2023
@aristath aristath added the [Type] Performance Related to performance efforts label May 31, 2023
@aristath aristath marked this pull request as ready for review May 31, 2023 11:12
@aristath aristath force-pushed the try/reduce-wp_array_get-use branch from 4c8c98f to cce29eb Compare June 6, 2023 08:26
@spacedmonkey
Copy link
Member

@aristath This looks good. I have one bit of feedback. This PR needs a rebase. Is there a plan for how this will be backported?

Copy link
Member

@felixarntz felixarntz left a comment

Choose a reason for hiding this comment

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

@aristath This looks mostly good, though I have one point that applies in several parts of the PR where I wonder why it's not optimized further.

If we already use isset, we should also avoid calling _wp_array_get() afterwards. Otherwise, the performance may technically be worse than before since we just add an isset call basically.

lib/block-supports/border.php Outdated Show resolved Hide resolved
lib/block-supports/border.php Outdated Show resolved Hide resolved
lib/block-supports/colors.php Outdated Show resolved Hide resolved
lib/block-supports/colors.php Outdated Show resolved Hide resolved
lib/block-supports/colors.php Outdated Show resolved Hide resolved
@felixarntz
Copy link
Member

@youknowriad
Copy link
Contributor

I'm curious, do we have any data that proves that this kind of refactoring improve some important metric. (Not talking about number of function calls or things like that but more LCP, TTFB or similar) I'm personally very suspicious of refactorings like have the potential of degrading the readability of code of unclear performance gains.

To clarify I'm all for this change if we have data that proves that it's effective.

@spacedmonkey
Copy link
Member

spacedmonkey commented Jun 20, 2023

@youknowriad Using XHProf, I did a profile on home page of tt3 themed site.

Screenshot 2023-06-20 at 17 08 07

This function gets called ALOT. Resulting in around 2% of page load. More complex / blocks on a page is more than it is called. So trying to avoid this function will improve performance. Using an isset, an in built php function is always going to be faster. I would argue that isset is more readable, as it is a core function.

@youknowriad
Copy link
Contributor

@spacedmonkey Can we compare the results of the current PR with trunk and see the results. The number of function calls is not important to me, the real metric is whether it makes the server response faster.

Using an isset, an in built php function is always going to be faster

I don't doubt that. Is there a way to rewrite the function internally to use isset?

I would argue that isset is more readable, as it is a core function.

I guess that's a bit subjective, but it's fine, I don't argue code style.


It may be fine for this particular change but what I'm trying to convey is that we should attempt refactoring code for performance without really testing before/after important metrics. IMO, the number of function calls is not a real metric.

@aristath aristath force-pushed the try/reduce-wp_array_get-use branch 2 times, most recently from a5cdc89 to 450a619 Compare June 23, 2023 07:56
@aristath
Copy link
Member Author

It may be fine for this particular change but what I'm trying to convey is that we should attempt refactoring code for performance without really testing before/after important metrics. IMO, the number of function calls is not a real metric.

I agree... Unfortunately my testing environment for PHP profiling is rather basic and unreliable (XDebug + Webgrind) so results vary from page-load to page-load and I can't accurately compare the results, there's a large margin of error. It's an OK environment for big changes, but not for smallish ones.
I know instinctively that this is a good performance improvement, but I can't provide accurate numbers. If someone has a more reliable profiling setup that would be great ❤️

@youknowriad
Copy link
Contributor

Excuse my devil's advocate tone, I'm just trying to make sure that we all collectively work on the right things and don't spend time on things that are not that important.

So, if we can't measure the improvement in a meaningful metric like TTFB, even if there's an improvement within the margin of error, is it really worth the hassle? I'm pretty sure you can take any function in our code base, unit test it and improve its performance by checking the unit test time for instance, but is it worth it?

Thanks for doing this work @aristath I really value performance work as you know, I just want to add to the discussion so we can get some common understanding on how we should approach performance work.

@aristath
Copy link
Member Author

I completely understand!
Even though my environment is not 100% accurate (and there's also an overhead from XDebug itself), it consistently shows an improvement in load-time.

Before (trunk):
Screenshot 2023-06-23 at 11 53 17 AM

After (this PR):
Screenshot 2023-06-23 at 11 52 50 AM

The 1st column is the function calls, the 2nd column is time in milliseconds.
There's an improvement of ~10ms on my localhost. I ran the tests multiple times and though the numbers vary slightly, the difference is there and ranges from 7 to 12ms.
Due to XDebug's overhead, the total page-load is ~3s, so in theory I'd expect on a normal production site this PR would improve performance by ~1-2ms.

I really value performance work as you know, I just want to add to the discussion so we can get some common understanding on how we should approach performance work.

It would be really nice if we come up with a proper methodology to handle performance improvements... Right now we just test things and whatever catches our attention is what we work on 😅

@jrfnl
Copy link
Member

jrfnl commented Jun 23, 2023

It would be really nice if we come up with a proper methodology to handle performance improvements... Right now we just test things and whatever catches our attention is what we work on

Might be worth it to look into getting a BlackFire account ?

@youknowriad
Copy link
Contributor

We already have https://www.codevitals.run/project/wordpress with some metrics, that's a good start. We should add good metrics that represent meaningful bottlenecks for user interactions.

@aristath aristath force-pushed the try/reduce-wp_array_get-use branch from a808375 to 2c618ca Compare July 25, 2023 08:46
@github-actions
Copy link

github-actions bot commented Jul 25, 2023

This pull request has changed or added PHP files. Please confirm whether these changes need to be synced to WordPress Core, and therefore featured in the next release of WordPress.

If so, it is recommended to create a new Trac ticket and submit a pull request to the WordPress Core Github repository soon after this pull request is merged.

If you're unsure, you can always ask for help in the #core-editor channel in WordPress Slack.

Thank you! ❤️

View changed files
❔ lib/block-supports/background.php
❔ lib/block-supports/border.php
❔ lib/block-supports/colors.php
❔ lib/block-supports/dimensions.php
❔ lib/block-supports/layout.php
❔ lib/block-supports/position.php
❔ lib/block-supports/settings.php
❔ lib/block-supports/spacing.php
❔ lib/block-supports/typography.php
❔ lib/class-wp-duotone-gutenberg.php
❔ lib/class-wp-theme-json-gutenberg.php
❔ lib/class-wp-theme-json-resolver-gutenberg.php
❔ lib/experimental/blocks.php

@godismyjudge95
Copy link

I don't mean to barge in on a random PR, but now that WordPress 6.3+ only supports PHP 7.0+ a lot of this could be cleaned up even further with the null coalescing operator.

@aristath
Copy link
Member Author

aristath commented Sep 19, 2023

Thank you for the comments and the review @andrewserong!
I addressed the feedback 👍

@aristath aristath changed the title Reduce the use of the _wp_array_get function Performance improvement: Reduce the use of the _wp_array_get function Sep 19, 2023
@aristath aristath enabled auto-merge (squash) September 19, 2023 08:09
@aristath aristath merged commit 20a6fd4 into trunk Sep 19, 2023
49 checks passed
@aristath aristath deleted the try/reduce-wp_array_get-use branch September 19, 2023 08:19
@github-actions github-actions bot added this to the Gutenberg 16.7 milestone Sep 19, 2023
@andrewserong
Copy link
Contributor

Thanks for the follow-ups @aristath, those lines in the colors block support are much more readable now, that looks great 👍
Nice work landing this change! 🎉

@tellthemachines
Copy link
Contributor

@aristath are you able to create a Trac ticket and open a PR in wordpress-develop to sync these changes to core?

@aristath
Copy link
Member Author

aristath commented Sep 20, 2023

@tellthemachines I already started the PR yesterday in WordPress/wordpress-develop#5244, as soon as tests pass I'll create the ticket on trac and submit the patch 👍

pento pushed a commit to WordPress/wordpress-develop that referenced this pull request Sep 26, 2023
…erformance.

`_wp_array_get()` is an expensive function, and it's called thousands of times on each page view on the front end. While the function performance was slightly improved in #58376, it is still called more times than it should be.

This commit aims to further optimize its usage:
* In many cases, `_wp_array_get()` can be replaced with a much simpler and faster `isset()` check.
* The `isset()` function is capable of checking nested arrays, so `isset( $foo['a']['b']['c'] )` will return false even if `$foo['a']` is unset, without throwing any errors or warnings.
* When `_wp_array_get()` cannot be directly replaced with `isset()`, it would be good practice to wrap it in an `isset()` function so that `_wp_array_get()` only runs when it needs to.

Original PR from Gutenberg repository:
* [WordPress/gutenberg#51116 #51116 Performance improvement: Reduce the use of the _wp_array_get() function]

Follow-up to [55851], [56382].

Props aristath, jrf, spacedmonkey, mukesh27, swissspidy, hellofromTonya.
Fixes #59405.

git-svn-id: https://develop.svn.wordpress.org/trunk@56709 602fd350-edb4-49c9-b593-d223f7449a82
markjaquith pushed a commit to WordPress/WordPress that referenced this pull request Sep 26, 2023
…erformance.

`_wp_array_get()` is an expensive function, and it's called thousands of times on each page view on the front end. While the function performance was slightly improved in #58376, it is still called more times than it should be.

This commit aims to further optimize its usage:
* In many cases, `_wp_array_get()` can be replaced with a much simpler and faster `isset()` check.
* The `isset()` function is capable of checking nested arrays, so `isset( $foo['a']['b']['c'] )` will return false even if `$foo['a']` is unset, without throwing any errors or warnings.
* When `_wp_array_get()` cannot be directly replaced with `isset()`, it would be good practice to wrap it in an `isset()` function so that `_wp_array_get()` only runs when it needs to.

Original PR from Gutenberg repository:
* [WordPress/gutenberg#51116 #51116 Performance improvement: Reduce the use of the _wp_array_get() function]

Follow-up to [55851], [56382].

Props aristath, jrf, spacedmonkey, mukesh27, swissspidy, hellofromTonya.
Fixes #59405.
Built from https://develop.svn.wordpress.org/trunk@56709


git-svn-id: http://core.svn.wordpress.org/trunk@56221 1a063a9b-81f0-0310-95a4-ce76da25c4cd
github-actions bot pushed a commit to platformsh/wordpress-performance that referenced this pull request Sep 26, 2023
…erformance.

`_wp_array_get()` is an expensive function, and it's called thousands of times on each page view on the front end. While the function performance was slightly improved in #58376, it is still called more times than it should be.

This commit aims to further optimize its usage:
* In many cases, `_wp_array_get()` can be replaced with a much simpler and faster `isset()` check.
* The `isset()` function is capable of checking nested arrays, so `isset( $foo['a']['b']['c'] )` will return false even if `$foo['a']` is unset, without throwing any errors or warnings.
* When `_wp_array_get()` cannot be directly replaced with `isset()`, it would be good practice to wrap it in an `isset()` function so that `_wp_array_get()` only runs when it needs to.

Original PR from Gutenberg repository:
* [WordPress/gutenberg#51116 #51116 Performance improvement: Reduce the use of the _wp_array_get() function]

Follow-up to [55851], [56382].

Props aristath, jrf, spacedmonkey, mukesh27, swissspidy, hellofromTonya.
Fixes #59405.
Built from https://develop.svn.wordpress.org/trunk@56709


git-svn-id: https://core.svn.wordpress.org/trunk@56221 1a063a9b-81f0-0310-95a4-ce76da25c4cd
anton-vlasenko pushed a commit to anton-vlasenko/wordpress-develop that referenced this pull request Sep 29, 2023
…erformance.

`_wp_array_get()` is an expensive function, and it's called thousands of times on each page view on the front end. While the function performance was slightly improved in #58376, it is still called more times than it should be.

This commit aims to further optimize its usage:
* In many cases, `_wp_array_get()` can be replaced with a much simpler and faster `isset()` check.
* The `isset()` function is capable of checking nested arrays, so `isset( $foo['a']['b']['c'] )` will return false even if `$foo['a']` is unset, without throwing any errors or warnings.
* When `_wp_array_get()` cannot be directly replaced with `isset()`, it would be good practice to wrap it in an `isset()` function so that `_wp_array_get()` only runs when it needs to.

Original PR from Gutenberg repository:
* [WordPress/gutenberg#51116 #51116 Performance improvement: Reduce the use of the _wp_array_get() function]

Follow-up to [55851], [56382].

Props aristath, jrf, spacedmonkey, mukesh27, swissspidy, hellofromTonya.
Fixes #59405.

git-svn-id: https://develop.svn.wordpress.org/trunk@56709 602fd350-edb4-49c9-b593-d223f7449a82
@SiobhyB SiobhyB removed the Needs PHP backport Needs PHP backport to Core label Oct 3, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Type] Performance Related to performance efforts
Projects
No open projects
Status: Done
Development

Successfully merging this pull request may close these issues.

10 participants