Skip to content

Conversation

@Michelleeby
Copy link

@Michelleeby Michelleeby commented Oct 14, 2025

Summary

This PR introduces a patch for this Trac ticket: https://core.trac.wordpress.org/ticket/64093. It provides a mechanism to cache what is learned from processed directives and it refactors _process_directives to use this cache when it exists.

Trac ticket: https://core.trac.wordpress.org/ticket/64093

Patch Iterations

Version 1

This version of the patch attempted a "pre-compute and cache" strategy. However, when working through the test failures, it was found that its architecture was not going to be easily compatible with that of the WP_HTML_Tag_Processor. Since it doesn't seem like there is a way to "jump to position X" without scanning from the beginning. Byte offsets shift as HTML is modified, and bookmarks are designed for single-pass processing, not cross-rendering reuse.

Version 2

To address this issue with Version 1, Version 2 of the patch provides a mechanism to store directive metadata, rather than attempting to cache the entire template.

Running Experiments

Download, install, and activate the interactive-stress-test plugin. When activated, the plugin creates three posts: "Stress Test – Light", "Stress Test – Medium", and "Stress Test – Heavy". To run the experiment suite, which by default runs a batch of 10 requests on each post, navigate to the frontend of the post "Stress Test – Light" and click "Run Batch Test".

Screenshot 2025-10-25 140359-run-batch-tests

When prompted, click "OK" to continue to the medium post. Wait for the medium tests to finish and when prompted again, click "OK" to continue to the heavy post. When the heavy test finishes, you will be prompted to download the results as a CSV. The results can also be viewed in browser console.


This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

…aching and pre-computation.

This commit introduces a caching mechanism for `data-wp-each` template blueprints, improving performance by pre-computing and storing template structures. The `data_wp_each_processor` method has been refactored to utilize this cache.
@github-actions
Copy link

github-actions bot commented Oct 14, 2025

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props michelleeby, darerodz.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@github-actions
Copy link

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • The Plugin and Theme Directories cannot be accessed within Playground.
  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

Originally the patch suggested creating bookmarks at opening tags but this didn't correctly account for how the API handles opening and closing pairs and the traversal stack state.

This commit revises the patch so that the blueprint is now a complete, ordered list of every tag (opening and closing) that next_tag() would visit. When we iterate through this list and seek() to each bookmark, we are replicating the original traversal. This ensures that the internal state of the processor (like its tag stack for handling nested elements) is always correct when process_directives is called.

The critical optimization is still in place. The expensive part—the while loop that repeatedly scans the HTML string—is gone from the main foreach loop. It's replaced by a loop that performs a series of highly efficient seek() operations. Effectively replacing a string-scanning operation with a much faster array iteration and direct memory jump.
… ticket/64093-interactivity-api-performance-bottleneck-in-data_wp_each_processor-due-to-repeated-template-parsing
…ach` processing.

Previous attempts to optimize `WP_Interactivity_API::data_wp_each_processor` included a "pre-compute and cache" strategy. Instead of re-parsing the template on every iteration, we can build a "blueprint" of the template once, cache it, and then use it to efficiently process each item. However, this solution did not consider how the WP_HTML_Tag_Processor is designed.

The WP_HTML_Tag_Processor architecture works by:

- Sequentially scanning HTML to find tags (via strpos, strcspn, etc.)
- Tracking positions as byte offsets
- Modifying HTML through WP_HTML_Text_Replacement objects

There's no way to "jump to position X" without scanning from the beginning. Byte offsets shift as HTML is modified, and bookmarks are designed for single-pass processing, not cross-rendering reuse.

Looking more carefully at the bottleneck, the real issue is that for EACH item in the array, we:

1. Create a new WP_Interactivity_API_Directives_Processor
2. Call $p->next_tag() repeatedly (which does expensive string scanning)
3. Call get_attribute_names_with_prefix('data-wp-') for each tag
4. Parse directive names and extract values
5. Evaluate directives

The optimization opportunity is steps 2-4, not step 5. We can't avoid re-scanning the HTML (that's how the Tag Processor works), but we can cache what we learned about where directives are and what they are.

This commit introduces the `WP_Interactivity_API_Directive_Cache` class to cache pre-parsed directive information, significantly reducing the O(N×M) complexity of rendering templates with multiple items. The caching mechanism stores directive metadata, improving performance during repeated template rendering.

Additionally, the `_process_directives` method is updated to utilize this cache, enhancing efficiency. Tests are added to ensure the correctness of the caching mechanism and its integration with existing functionality.
…r an additional item in the template while retaining the template tag for client-side hydration.
…pi-directive-cache.php` to improve code cleanliness and fix code sniff errors
@Michelleeby Michelleeby force-pushed the ticket/64093-interactivity-api-performance-bottleneck-in-data_wp_each_processor-due-to-repeated-template-parsing branch from e183a7e to 7280cc9 Compare October 25, 2025 12:04
@DAreRodz
Copy link

Hello, @Michelleeby! 👋

I've been testing this PR following the steps you provided. Below are the results I got, and there doesn't seem to be much difference between them. What results do you obtain, @Michelleeby? Asking just in case I did something wrong while running the tests. 😄

WordPress:trunk e0558c2

Variation Runs Avg Time (ms) Min Time (ms) Max Time (ms)
light 10 291.82 285.40 302.20
medium 10 798.53 770.70 836.60
heavy 10 1518.23 1476.90 1550.70

Michelleeby:trunk ceae14b

Variation Runs Avg Time (ms) Min Time (ms) Max Time (ms)
light 10 294.18 283.70 305.20
medium 10 780.25 765.50 807.20
heavy 10 1502.56 1452.50 1543.40

Michelleeby:ticket/64093-interactivity... 7280cc9

Variation Runs Avg Time (ms) Min Time (ms) Max Time (ms)
light 10 298.30 292.60 308.70
medium 10 788.31 773.50 818.30
heavy 10 1508.38 1480.90 1592.30

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.

2 participants