Skip to content

prepend_to_selector: optimized with str_replace()#76556

Open
josephscott wants to merge 1 commit into
WordPress:trunkfrom
josephscott:performance/prepent-to-selector
Open

prepend_to_selector: optimized with str_replace()#76556
josephscott wants to merge 1 commit into
WordPress:trunkfrom
josephscott:performance/prepent-to-selector

Conversation

@josephscott

@josephscott josephscott commented Mar 16, 2026

Copy link
Copy Markdown
Contributor

After talking it over with Claude Code, it turns out we can squeeze out a bit more performance from prepend_to_selector() by getting str_replace() to do most of the work.

For one site I was using to test with I found that there were over 4,000 calls to prepend_to_selector() just to bootstrap WordPress. Each one runs very quickly, but with that volume it would be nice to make it a bit faster.

For this test with over 4,000 calls, here are the before and after numbers:

Before:

  • p75: 0.008032 ms

After:

  • p75: 0.006198 ms

While those raw times are going to vary depending on the site and server, the relative time is what I would focus on. That 0.001834 ms improvement is 22.8%.

I had curl make 100 requests and had it log the time via:

$start = hrtime( true );
$elements = static::get_block_element_selectors( $root_selector );
error_log( ( hrtime( true ) - $start ) / 1e6 );

Then sorted the logged results with:

tail -n 100 /tmp/php-errors | awk '{print $NF}' | sort -n | awk 'NR==50{p50=$1} NR==75{p75=$1} END{print "p50:",p50,"\np75:",p75}'

I also did a comparison run to make sure that all 4,000+ results were the same before and after this change. To protect prepend_to_selector() in the future I also had Claude Code generate tests.

There is also a completely stand alone benchmark script to compare both of these at https://gist.github.com/josephscott/254521a43dee3300448ef288dd2ea1f2

While not my original focus for this change, it does have a small reduction in the amount of memory used.

Before:

  • p75: 17,874,280 bytes

After:

  • p75: 17,870,680 bytes

The 3,600 bytes is very minor, but nice to see.

Another thing that I considered was getting rid of the separate method call entirely, since it is only called in one place - inside the get_block_element_selectors() loop. Now that it is only 4 lines long there is not much point to calling out to separate method. Again, trying to squeeze everything we can out of something called 4,000+ times. But this method has been around for a few years, so I didn't worry about that.

See #50266 for the original PR that created prepend_to_selector().

What?

Closes

Why?

How?

Testing Instructions

Testing Instructions for Keyboard

Screenshots or screencast

Before After

Use of AI Tools

Claude Code was used to come up with ideas for performance improvements and it wrote the new tests.

After talking it over with Claude Code, it turns out we can squeeze out a
bit more performance from `prepend_to_selector()` by getting `str_replace()`
to do most of the work.

For one site I was using to test with I found that there were over 4,000
calls to `prepend_to_selector()` just to bootstrap WordPress.  Each one
runs very quickly, but with that volume it would be nice to make it a bit
faster.

For this test with over 4,000 calls, here are the before and after numbers:

Before:
- p75: 0.008032 ms

After:
- p75: 0.006198 ms

While those raw times are going to vary depending on the site and server,
the relative time is what I would focus on.  That 0.001834 ms improvement
is 22.8%.

I had curl make 100 requests and had it log the time via:

```php
$start = hrtime( true );
$elements = static::get_block_element_selectors( $root_selector );
error_log( ( hrtime( true ) - $start ) / 1e6 );
```

Then sorted the logged results with:

```bash
tail -n 100 /tmp/php-errors | awk '{print $NF}' | sort -n | awk 'NR==50{p50=$1} NR==75{p75=$1} END{print "p50:",p50,"\np75:",p75}'
```

I also did a comparison run to make sure that all 4,000+ results were the
same before and after this change.  To `protect prepend_to_selector()` in
the future I also had Claude Code generate tests.

There is also a completely stand alone benchmark script to compare both of
these at https://gist.github.com/josephscott/254521a43dee3300448ef288dd2ea1f2

While not my original focus for this change, it does have a small reduction
in the amount of memory used.

Before:
- p75: 17,874,280 bytes

After:
- p75: 17,870,680 bytes

The 3,600 bytes is veryminor, but nice to see.

Another thing that I considered was getting rid of the separate method
call entirely, since it is only called in one place - inside the
`get_block_element_selectors()` loop.  Now that it is only 4 lines long there
is not much point to calling out to separate method.  Again, trying to
squeeze everything we can out of something called 4,000+ times.  But this
method has been around for a few years, so I didn't worry about that.

See WordPress#50266 for the original PR
that created `prepend_to_selector()`.
@github-actions

Copy link
Copy Markdown

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.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: josephscott <josephscott@git.wordpress.org>

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

@github-actions github-actions Bot added the First-time Contributor Pull request opened by a first-time contributor to Gutenberg repository label Mar 16, 2026
@github-actions

Copy link
Copy Markdown

👋 Thanks for your first Pull Request and for helping build the future of Gutenberg and WordPress, @josephscott! In case you missed it, we'd love to have you join us in our Slack community.

If you want to learn more about WordPress development in general, check out the Core Handbook full of helpful information.

@josephscott josephscott added the [Type] Performance Related to performance efforts label Mar 16, 2026
@Mamaduka Mamaduka added Global Styles Anything related to the broader Global Styles efforts, including Styles Engine and theme.json and removed First-time Contributor Pull request opened by a first-time contributor to Gutenberg repository labels Mar 17, 2026
@josephscott josephscott requested a review from youknowriad March 26, 2026 14:51

@youknowriad youknowriad left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The change makes sense to me. Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Global Styles Anything related to the broader Global Styles efforts, including Styles Engine and theme.json [Type] Performance Related to performance efforts

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants