ext/standard: speed up php_url_parse_ex2 by ~12%#31
Open
Conversation
Three related changes to ext/standard/url.c targeting the ctype macros on the parse_url hot path. On a 17-URL mix (17M parses per run, CPU pinned, same-session A/B), median wall time drops from 1.90s to 1.68s, a ~12% reduction and ~13% throughput increase (8.94M/s to 10.10M/s). 1. php_replace_controlchars replaces its iscntrl() call with an inline `c < 0x20 || c == 0x7f` comparison. Callgrind showed iscntrl at ~14% of total instructions on a realistic URL workload; glibc's iscntrl goes through __ctype_b_loc() per byte for a TLS lookup and table deref, which defeats auto-vectorization. URL components are bytes, not locale-dependent text, so C/POSIX semantics are what we want regardless of the process locale. The Zend language scanner uses the same pattern (yych <= 0x1F). This runs once per component per parse, up to 7 times. 2. The scheme-validation walk uses isalpha/isdigit which have the same __ctype_b_loc tax. I extracted the check into php_url_is_scheme_char with an inline ASCII test: ((c | 0x20) - 'a' < 26u) || (c - '0' < 10u) for the letter/digit half, plus the three literal comparisons for + - and . The scheme loop runs once per byte of the scheme on every parse. A helper php_url_is_ascii_digit covers the two isdigit call sites in the port-scan loops (one in the mailto-branch port probe, one in the parse_port fallback). 3. The three branches that allocate ret->scheme all followed zend_string_init with a php_replace_controlchars call. The scheme loop above has already rejected any byte that isn't in [a-zA-Z0-9+.-], so the control-char scan on scheme is dead work. Removed from all three sites. No behavior change: the inline comparisons are identical in behavior to the ctype macros in C/POSIX, and URL bytes are never locale-dependent. I checked that contaminated inputs like http://ex\x7fample.com/p\x1fath still get their control bytes replaced with underscores.
24463fe to
e670fd7
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three related ctype-macro replacements in
ext/standard/url.cthatspeed up
php_url_parse_ex2and thereforeparse_url()by ~12% on arealistic URL mix. Per-change breakdown is in the commit message.
This also feeds the
php_parse_urlbackend inext/uri, which wrapsphp_url_parse_ex2and is the default parser returned byphp_uri_get_parser(NULL)for streams,filter_var(FILTER_VALIDATE_URL),soap, http/ftp wrappers, and the internal
php_uri_parse()C API.Benchmark
17 URL shapes (plain http/https, deep paths, with query/fragment, with
userinfo, IPv4, IPv6, %-encoded path, ftp, mailto, data, file, relative).
1M iterations per run, 17M total
parse_url()calls per benchmark, CPUpinned via
taskset -c 0, same-session A/B (stash + rebuild + reruneach direction).
parse_url()fullWhat's in the patch
php_replace_controlcharsreplacesiscntrl()with inlinec < 0x20 || c == 0x7f. glibc'siscntrlhits__ctype_b_loc()per byte; callgrind showed it at ~14% of total instructions on a
realistic URL workload. URL components are bytes, not
locale-dependent text, and the Zend scanner uses the same inline
pattern (
yych <= 0x1F).Scheme-validation walk swaps
isalpha(*p) && isdigit(*p)forphp_url_is_scheme_char((unsigned char) *p), which does((c | 0x20) - 'a' < 26u) || (c - '0' < 10u)plus the threeliteral character checks. Same change for the two
isdigitsitesin the port-scan loops via
php_url_is_ascii_digit.Skipped
php_replace_controlcharsonret->schemein all threeallocation branches. The scheme walk above has already rejected any
byte outside
[a-zA-Z0-9+.-], so the control-char scan can't findanything to replace.