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

Store merged Theme JSON data in memory. #54742

Conversation

joemcgill
Copy link
Member

@joemcgill joemcgill commented Sep 22, 2023

What?

This adds an additional static cache to WP_Theme_JSON_Resolver_Gutenberg that stores the merged data for each origin in memory to avoid recreating unnecessary WP_Theme_JSON_Gutenberg objects each time a function needs to get Theme JSON data.

Why?

See https://core.trac.wordpress.org/ticket/57789

How?

Testing Instructions

Running a profiler, like XHProf on a pageload before and after applying this change should demonstrate a reduction in calls to each of the getter methods for each origin, along with a reduction in merges. See example from the core repo: https://core.trac.wordpress.org/ticket/57789#comment:23

Testing Instructions for Keyboard

n/a

Screenshots or screencast

This adds an additional static cache to `WP_Theme_JSON_Resolver_Gutenberg` that stores the merged data for each origin in memory to avoid recrating unnecessary `WP_Theme_JSON_Gutenberg` objects each time a function needs to get Theme JSON data.

See https://core.trac.wordpress.org/ticket/57789
@joemcgill joemcgill requested a review from oandregal September 22, 2023 16:33
@joemcgill joemcgill added the [Type] Performance Related to performance efforts label Sep 22, 2023
@joemcgill
Copy link
Member Author

These changes were originally made in WordPress/wordpress-develop#5024. This applies the latest PR feedback and makes changes to the Gutenberg repo so we could test this in the plugin prior to a core merge.

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.

Per my previous feedback on WordPress/wordpress-develop#5024, LGTM!

}

// Map origins to block cache values.
$cache_map = array(
Copy link
Member

Choose a reason for hiding this comment

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

What if we update the $blocks_cache to have the proper keys (default instead of core, and custom instead of user) so we don't need this?

Copy link
Member

Choose a reason for hiding this comment

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

@oandregal While I agree this would be ideal, I think this should be subject to another separate issue/PR. There may be backward compatibility implications from changing the cache names - probably not, but that would be a fix not really related to what this PR is aiming to achieve.

Copy link
Member Author

Choose a reason for hiding this comment

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

If there's a path to making these consistent, I'm all for it, but was out of scope of what I was trying to show here, so I just worked with what existed.

@@ -597,26 +634,58 @@ public static function get_merged_data( $origin = 'custom' ) {
_deprecated_argument( __FUNCTION__, '5.9.0' );
}

if ( ! in_array( $origin, WP_Theme_JSON_Gutenberg::VALID_ORIGINS, true ) ) {
Copy link
Member

Choose a reason for hiding this comment

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

Do we need this? Doesn't it work the same as it works today?

Copy link
Member

Choose a reason for hiding this comment

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

Technically, I agree it's not needed now more than it was needed before, but it's a good practice to have. We should make sure developers passing invalid values to the function either explicitly fails or "forces" a useful result. Having this here ensures the latter.

Copy link
Member Author

Choose a reason for hiding this comment

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

This is just a bit of defensive coding to make the rest of the logic more predictable if someone passes an invalid origin value. I added it as a best practice to avoid potential bugs and edge cases.

null !== static::$merged[ $origin ]
&& static::has_same_registered_blocks( $cache_map[ $origin ] )
// Ensure theme supports data is fresh before returning cached data for theme and custom origins.
&& ( ! in_array( $origin, array( 'theme', 'custom' ), true ) || static::get_theme_supports_data() === static::$theme_support_data )
Copy link
Member

Choose a reason for hiding this comment

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

I presume the logic above to convert unknown origins to custom was necessary for this line. Thinking we can do this instead:

// theme and custom origins require access to theme supports,
// so the cached merged data will only be used for those origins if the current supports are equal to the cached ones.
( in_array( $origin, array( 'default', 'blocks' ), true ) || static::get_theme_supports_data() === static::$theme_support_data )

Copy link
Member Author

Choose a reason for hiding this comment

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

This is one of the places where an invalid $origin value would be problematic, but there could be others. I considered writing this as you suggested, but decided it was likely better to explicitly check for the origin values where the theme supports data check is required, rather than the inverse. Both get the same result though.

Copy link
Member

@oandregal oandregal left a comment

Choose a reason for hiding this comment

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

I left some minor comments – definitely not something that blocks the PR from landing.

In terms of failure scenarios, I cannot come up with any, so this change is good.

I'm conflicted about whether it makes sense to do it, though. Looking at the performance analysis you've posted here and here, the user metrics for velocity don't seem to be impacted. Though, it does double the memory consumed by caching both the single data origin (e.g., theme) and the merged data origin (e.g., up to theme). I feel the memory-velocity trade-off is a bit unbalanced (much memory for no gains in velocity).

At the same time, I reckon that some other scenarios we are not testing may have many more nodes (settings/styles/blocks in theme.json), hence, they could see bigger wins.

I'm on the fence about this one. You have been the most invested in this research, so it's only fair that it's you who decide whether it makes sense or not. In any case, I appreciate the time and commitment to investigate it. Thanks!

@felixarntz
Copy link
Member

@oandregal I agree this ends up being more of a micro optimization, but I don't see any harm in implementing it. To me it makes sense to have a merged cache alongside the individual caches. Generally, I am more on the side of prioritizing performance (time) over memory (unless of course the tradeoff is large/problematic).

Copy link
Member Author

@joemcgill joemcgill left a comment

Choose a reason for hiding this comment

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

Thanks for the review, @oandregal. I'm similarly on the fence about whether this change is worth making. It does lead to some improvements by avoiding repeated calls to get the data from each origin and merge them together, but most of the time spent in all scenarios is in the initial calculation for each origin rather than the repeated calls.

That said, the most impactful change will be if we can reliably add some persistence to the caching strategy for either the merged data or individual origins and having this in place to start testing can help us discover potential issues with a non-persistent cache prior to introducing any persistence, so it may be worth testing as an experiment to discover scenarios where cache invalidation needs to be considered in the future.

@@ -597,26 +634,58 @@ public static function get_merged_data( $origin = 'custom' ) {
_deprecated_argument( __FUNCTION__, '5.9.0' );
}

if ( ! in_array( $origin, WP_Theme_JSON_Gutenberg::VALID_ORIGINS, true ) ) {
Copy link
Member Author

Choose a reason for hiding this comment

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

This is just a bit of defensive coding to make the rest of the logic more predictable if someone passes an invalid origin value. I added it as a best practice to avoid potential bugs and edge cases.

null !== static::$merged[ $origin ]
&& static::has_same_registered_blocks( $cache_map[ $origin ] )
// Ensure theme supports data is fresh before returning cached data for theme and custom origins.
&& ( ! in_array( $origin, array( 'theme', 'custom' ), true ) || static::get_theme_supports_data() === static::$theme_support_data )
Copy link
Member Author

Choose a reason for hiding this comment

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

This is one of the places where an invalid $origin value would be problematic, but there could be others. I considered writing this as you suggested, but decided it was likely better to explicitly check for the origin values where the theme supports data check is required, rather than the inverse. Both get the same result though.

@joemcgill
Copy link
Member Author

Closing for now as this didn't seem to have a bit impact on overall performance.

@joemcgill joemcgill closed this Apr 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs PHP backport Needs PHP backport to Core [Type] Performance Related to performance efforts
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants