Opened 5 weeks ago
Last modified 33 hours ago
#65846 assigned defect (bug)
Hoist strlen() out of the class_list() loop condition
| Reported by: | mukesh27 | Owned by: | mukesh27 |
|---|---|---|---|
| Priority: | normal | Milestone: | 7.2 |
| Component: | HTML API | Version: | |
| Severity: | normal | Keywords: | has-patch |
| Cc: | Focuses: | performance |
Description
WP_HTML_Tag_Processor::class_list() scans the class attribute one name at a time. Its loop calls strlen( $class ) twice per iteration - once in the while condition, and once more in the guard that runs after boundary characters are skipped:
$at = 0;
while ( $at < strlen( $class ) ) {
// Skip past any initial boundary characters.
$at += strspn( $class, " \t\f\r\n", $at );
if ( $at >= strlen( $class ) ) {
return;
}
// ...
}
$class is a local assigned once from get_attribute( 'class' ) and never reassigned inside the loop, so its length is invariant. A while condition is evaluated once per iteration plus once more to terminate, so scanning an attribute with n class names makes 2n + 1 calls to recompute a constant.
class_list() is the generator behind has_class(), so it runs for every tag whose classes are inspected during HTML processing — block supports, render_block() filters and the block hooks all lean on it.
Change History (7)
This ticket was mentioned in PR #12964 on WordPress/wordpress-develop by @mukesh27.
5 weeks ago
#2
- Keywords has-patch added; needs-patch removed
@westonruter commented on PR #12964:
5 weeks ago
#3
I recall this coming up before, that strlen() may be a different case than count() with an array. PHP may store an internal string length more readily than it does for arrays.
#4
@
5 weeks ago
PR 8076 is a related optimization/simplification I've been sitting on. I'm tempted to attach it to this ticket if we can make it a bit more generic.
@jonsurrell commented on PR #12964:
5 weeks ago
#5
I've also heard that from @dmsnell several times and decided to verify it myself, or at least have Copilot verify it.
It says:
\[
strlen()\] is a built-in internal function, and when the optimizer can prove the argument is already a string, it can even fold it to a direct string-length read at compile time.
…
Why it’s cheap:
- PHP strings store their length alongside the string buffer, so length retrieval is O(1).
- For non-constant cases, the runtime still has to perform a function/opcode dispatch, parameter handling, and type checks.
- So the length access itself is extremely cheap…
Referencing:
---
Previously, we'd relied on this extensively across the HTML API instead of using $strlen variables, although that's changed and there are plenty examples of storing the string length in a variable now. I don't think there's anything harmful or wrong with this change although the performance difference is likely negligible.
This ticket was mentioned in PR #8076 on WordPress/wordpress-develop by @jonsurrell.
33 hours ago
#7
This is a simplification and optimization of the class_list loop based on some work in https://github.com/WordPress/wordpress-develop/pull/7857.
The important change here is that the position ($at) advances past any whitespace characters before the while condition is checked. _The loop never begins at whitespace characters_ allowing for a few checks to be eliminated and the overall loop logic to be simplificed. Whitespace is skipped before the loop is entered and when $at is adjusted at the end of the loop.
This allows for some conditionals to be removed from the loop because they become invariants. The loop is always exited by the while condition after the entire string has been processed.
Trac ticket: https://core.trac.wordpress.org/ticket/65846
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)
Trac ticket: https://core.trac.wordpress.org/ticket/65846
## Use of AI Tools
AI assistance: Yes
Tool(s): Claude
Model(s): Opus 5
Used for: Initial code skeleton; final implementation was reviewed and edited by me.