Repository navigation
ext/standard: Add php_next_utf8_char_fast() - #24283
ArtUkrainskiy wants to merge 1 commit into
Conversation
ca35c07 to
63792a2
Compare
adapik
left a comment
There was a problem hiding this comment.
I went through the implementation and I find the approach reasonable: the inline path only takes sequences that are valid by construction, and everything else falls back to the unchanged php_next_utf8_char(), so the error semantics can't diverge. I also ran it through my own benchmarks and the results are solid.
Regarding overlap with my other PRs (24283 and 24282):
- The dirty table is a small, self-contained change to the clean/dirty classification of a byte. I think it stands on its own and combines well with this PR, since this PR changes what happens after a byte is classified as dirty (non-ASCII), not the classification itself.
- I don't see any overlap with the SIMD work either. The two optimizations operate on different branches of php_json_escape_string() (the SIMD one on runs of clean ASCII, this one on the non-ASCII decode), so they complement each other.
Depending on which one is merged first, I'm happy to rebase mine, that's not a problem.
| @@ -0,0 +1,217 @@ | |||
| --TEST-- | |||
| json_encode() with valid and invalid UTF-8 sequences around the two-, three- and four-byte boundaries | |||
There was a problem hiding this comment.
The new test only covers json_encode(). Since the validation logic is now duplicated between html.c and html.h, a small phpt exercising the same boundary sequences through utf8_decode() would stop the two copies from drifting apart silently.
63792a2 to
f331165
Compare
|
@adapik Thanks for reviewing it and for running it through your benchmarks. Test: I added ext/standard/tests/strings/utf8_decode_sequences.phpt with the same boundary sequences through utf8_decode(), plus overlong encodings of é, which utf8_decode() makes visible as é instead of "?". The expected output comes from master. To check that the two tests catch drift between the copies, I mutated the inline decoder one condition at a time:
Header: I put the function in html.h next to php_next_utf8_char() because of the earlier review of #18126, where the suggestion was to optimize the existing decoder rather than add a new one elsewhere (#18126 (comment)). A small dedicated header, with php_next_utf8_char() staying exported from html.c, is a change of a few lines here. @bukka, as the owner of ext/standard and ext/json: do you prefer the inline decoder in html.h, as it is now, or in a separate header (for example ext/standard/php_utf8.h)? I'll update the PR either way. |
|
@bukka @iluuu1994 could you take a look when you get a chance? The only open point is the function name, happy to rename. It adds php_next_utf8_char_inline() in html.h and uses it in json_encode. Stacks cleanly with #24666. |
bukka
left a comment
There was a problem hiding this comment.
I think this look reasonable. Just naming could be better as noted. I merged #24145 so get_next_char is now always inlined which slightly reduced the initial motivation for this. It should still improve things but it would be good to re-measure it. It should also update the commit message which is no longer completely true...
|
|
||
| /* Same contract as php_next_utf8_char(). Decodes valid UTF-8 inline; invalid input goes | ||
| * to php_next_utf8_char(). */ | ||
| static zend_always_inline unsigned int php_next_utf8_char_inline( |
There was a problem hiding this comment.
I don't like this name - Can you rename it to php_next_utf8_char_fast or something more consistent.
There was a problem hiding this comment.
Renamed to php_next_utf8_char_fast(), remeasured and updated description
There was a problem hiding this comment.
The remaining ASCII/escaping regressions may be addressed by the scan changes in #23675. I'll check the combined results once this PR lands.
Decode valid UTF-8 at the call site in json_encode(), utf8_decode() and the xml extension. Invalid or truncated input falls back to the unchanged exported php_next_utf8_char(), preserving its error handling and ABI. get_next_char() is already always inlined into php_next_utf8_char(), but callers in other translation units still pay for the exported call.
f331165 to
df2b554
Compare
json_encode(),utf8_decode()and the xml extension decode one character at a time through the exportedphp_next_utf8_char(). This addsphp_next_utf8_char_fast()inhtml.hto decode valid UTF-8 at the call site. Invalid or truncated input falls back to the existing exported function, preserving its error handling and ABI.get_next_char()is already always inlined into the exported decoder. This still avoids the exported call for valid input. The measurements compare the change with master97335aa86f7, including #24145.Fresh release builds use the official Docker-image compiler and linker flags, plus
-gand--enable-werror. No explicit alignment flags are used. Five alternating passes per case on Intel and fifteen on AMD. Unescaped Unicode rows useJSON_UNESCAPED_UNICODE. Negative = fewer cycles (faster).The full report includes instructions, all 26 inputs, pass ranges and regressions. Maximum absolute A/A median deviation: Intel 1.31%, AMD 0.23%. Differences comparable to the controls should not be treated as established gains or regressions. The earlier alignment-profile figures are archived as diagnostics.
The existing exhaustive byte-sequence checks and fuzz generators match master on all six Docker-profile builds. JSON, XML and string tests pass on GCC 13, clang 20 and GCC 15. I haven't rerun sanitizers or tested aarch64, 32-bit or MSVC builds.
Full results, exact flags and reproduction: https://github.com/ArtUkrainskiy/php-src-bench/tree/main/reports/json-encode-utf8-inline