Skip to content

Editor, Themes: Evaluate count() once per loop instead of once per iteration - #69

Draft
mukeshpanchal27 wants to merge 16 commits into
trunkfrom
perf/hoist-count
Draft

Editor, Themes: Evaluate count() once per loop instead of once per iteration#69
mukeshpanchal27 wants to merge 16 commits into
trunkfrom
perf/hoist-count

Conversation

@mukeshpanchal27

Copy link
Copy Markdown
Owner

Summary

A for loop condition is evaluated once per iteration, plus once more to terminate. When that condition is $i < count( $array ), PHP calls count() n + 1 times to walk an n-element array whose length never changes inside the body.

This moves the count into the loop initialiser at the four core sites that still re-evaluate it:

for ( $i = 0, $c = count( $array ); $i < $c; $i++ ) {

That form is already the dominant idiom in core — formatting.php:769/:780, class-wp-comment-query.php:1113, class-wpdb.php:3151, functions.php:2106, class-wp-filesystem-base.php:448, image-edit.php:673 and deprecated.php:1741 all use it. These were the remaining outliers.

Benchmark

100,000 loop runs per row, PHP 8.x:

Array size count() in condition count() hoisted Saved
3 elements 7.48 ms 6.53 ms 13%
10 elements 16.65 ms 12.71 ms 24%
50 elements 62.29 ms 50.95 ms 18%

To be clear about what this is and isn't: count() on a PHP array is O(1) — it reads a cached length — so this is not an algorithmic fix. What it removes is per-iteration function-call overhead (opcode dispatch, frame push, return) repeated n times. Modest per loop, but free, and it makes the fixed bound explicit.

Sites changed

File Line Note
src/wp-includes/blocks.php 620 register_block_type_from_metadata(), script handles. Runs for every registered block on every request — core alone registers ~100 blocks — and the enclosing foreach covers three script fields (editorScript, script, viewScript).
src/wp-includes/blocks.php 654 Same function, script module IDs.
src/wp-includes/blocks.php 690 Same function, style handles — three style fields (editorStyle, style, viewStyle).
src/wp-includes/class-wp-theme-json.php 5382 set_spacing_sizes(), relabelling spacing presets.

Safety

Hoisting is only valid if the body cannot change the array's length. Each site was checked:

  • blocks.php (all three)$scripts, $modules and $styles are local copies of a $metadata field. The body calls register_block_script_handle() / register_block_script_module_id() / register_block_style_handle() and appends to a separate accumulator ($processed_scripts etc.). The iterated array is never written to, and the registration helpers receive $metadata by value, so they cannot resize it either.
  • class-wp-theme-json.php — the body assigns $spacing_sizes[ $i ]['name'], overwriting a key on an existing element. It never adds or removes elements, so the count is invariant.

No behaviour change: for a fixed-length array, $i < count( $a ) and $i < $c where $c = count( $a ) are the same condition.

Deliberately not changed

  • formatting.php:3406wp_targeted_link_rel() has the same pattern with an invariant array, but was deprecated in 6.7.0 and calls _deprecated_function() on entry. Not worth the review surface.
  • for ( $i = count( $x ) - 1; $i >= 0; $i-- ) loopsclass-wp-http.php:746, html-api/class-wp-html-open-elements.php:645, html-api/class-wp-html-active-formatting-elements.php:200, html-api/class-wp-html-processor.php:944, nav-menu.php:1561, file.php:1630. These call count() in the initialiser, which already runs exactly once. Nothing to hoist.
  • Bundled third-party codeclass-pclzip.php and rss.php (MagpieRSS) have many sizeof()-in-condition loops, but are vendored libraries core does not maintain.

Testing instructions

php -l src/wp-includes/blocks.php
php -l src/wp-includes/class-wp-theme-json.php
vendor/bin/phpcs src/wp-includes/blocks.php src/wp-includes/class-wp-theme-json.php
npm run test:php -- --filter '(Blocks_Register|ThemeJson)'

Results on this branch:

  • php -l — no syntax errors in either file.
  • phpcs — clean, 0 errors / 0 warnings.
  • PHPUnit — OK (377 tests, 881 assertions).

This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See Contributing to WordPress for more information.

mukeshpanchal27 and others added 16 commits July 30, 2026 20:42
…eration.

A `for` condition runs once per iteration plus once to terminate, so
`$i < count( $array )` calls `count()` n + 1 times to walk an n-element array
whose length never changes. Compute the bound in the loop initialiser instead,
matching the idiom already used throughout core.

`register_block_type_from_metadata()` is the main beneficiary: it runs for every
registered block on every request, and the affected loops sit inside `foreach`
blocks covering three script fields and three style fields each.

In every case the iterated array is provably invariant across the loop body, so
there is no behaviour change.

Props mukesh.
The introduction of a return-type annotation for `absint()` created fatal errors in cases where the function returns a `float` value. This resulted when the value passed into the function is smaller than `PHP_INT_MIN`. Since PHP’s `int` type is unable to represent the magnitude of that number in the positive, it returns a `float` value instead.

Reverting the type annotation prevents the crashing, but additional follow-up is warranted to ensure that the function produces the expected return types.

Developed in: WordPress#12940
Discussed in: https://core.trac.wordpress.org/ticket/65826

Follow-up to [62647].

Props dmsnell, josephscott, westonruter.
Fixes #65826. See #64898.


git-svn-id: https://develop.svn.wordpress.org/trunk@63158 602fd350-edb4-49c9-b593-d223f7449a82
… workflows.

This commit extends r62891 to the two older reusable PHPUnit workflows.

That change added the `runs-on` override to `reusable-phpunit-tests-v3.yml` alone, so only branches calling that workflow can be redirected.

Branches 4.7 through 5.1 call `reusable-phpunit-tests-v1.yml`, and branches 5.2 through 5.8 call `reusable-phpunit-tests-v2.yml`, both at `@trunk`. Neither reads the variable, so setting `RUNNERS_NAME` at the repository or organization level has no effect on those twelve branches.

This applies the same one-line change to both files. With the variable unset, jobs run on `inputs.os` exactly as before, so there is no change by default.

Each of the three reusable PHPUnit workflows contains exactly one `runs-on`, so no other line in these files needs the same treatment.

Developed in WordPress#12847.

Follow-up to r62891, r62974, r63003.

Props lancewillett.
Fixes #65749.

git-svn-id: https://develop.svn.wordpress.org/trunk@63159 602fd350-edb4-49c9-b593-d223f7449a82
The `$query` variable comes from the `pre_user_query` filter args and is a `WP_User_Query` instance, the `assertInstanceOf( WP_User_Query::class, ... )` on the line above asserts exactly that. The docblock incorrectly declared it as `WP_User`.

Developed in WordPress#12834.

Follow-up to r59899.

Props Soean.
See #64894.

git-svn-id: https://develop.svn.wordpress.org/trunk@63160 602fd350-edb4-49c9-b593-d223f7449a82
Using the dedicated `assertNull()` assertion instead of `assertSame( null, $actual )` clarifies intent and produces more descriptive failure messages.

Developed in WordPress#12818.

Follow-up to r62969.

Props Soean.
See #64894.

git-svn-id: https://develop.svn.wordpress.org/trunk@63161 602fd350-edb4-49c9-b593-d223f7449a82
Co-authored-by: Weston Ruter <westonruter@gmail.com>
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.

3 participants