uri_normalizer_percent_decode_unreserved() and uri_normalizer_percent_upper() walked their input one byte at a time, appending every single byte to the output even when no percent sign was involved. For URIs that contain few or no escape sequences, which is the common case, this copies the whole string byte by byte.
Only '%' sequences need per-byte handling. Locate the next '%' with memchr() and copy the run preceding it in one memcpy(), then handle the escape sequence exactly as before. The output is unchanged, including the strict mode error behaviour for truncated or invalid sequences. This patch and the query sort change that follows were measured together: on a 24-core AMD EPYC 7413 with a 20 kB query string (427 parameters), gcc 8.5.0 -O2 -march=native and keep-alive, the CPU time per request spent in the six normalizers drops from 1137.52 us to 1062.53 us (-6.6%). The two were not measured separately; most of that comes from the next patch, which is why the numbers are quoted there. This is a pure optimization with no functional change, so the risk of regression is low. It is not needed in stable branches. Signed-off-by: Youngkwang Lee <[email protected]> --- src/uri_normalizer.c | 113 ++++++++++++++++++++++++++----------------- 1 file changed, 68 insertions(+), 45 deletions(-) diff --git a/src/uri_normalizer.c b/src/uri_normalizer.c index 12317f183..c24d95fd5 100644 --- a/src/uri_normalizer.c +++ b/src/uri_normalizer.c @@ -102,40 +102,52 @@ enum uri_normalizer_err uri_normalizer_percent_decode_unreserved(const struct is } while (istlen(scanner)) { - const char current = istshift(&scanner); + /* Only '%' sequences need per-byte handling, everything else is + * copied verbatim. Locating the next '%' with memchr() and + * copying the run before it in one go avoids walking the input + * one byte at a time without changing the output. + */ + const char *pct = memchr(istptr(scanner), '%', istlen(scanner)); + + if (!pct) { + memcpy(istend(output), istptr(scanner), istlen(scanner)); + output = ist2(istptr(output), istlen(output) + istlen(scanner)); + break; + } - if (current == '%') { - if (istlen(scanner) >= 2) { - if (ishex(istptr(scanner)[0]) && ishex(istptr(scanner)[1])) { - char hex1, hex2, c; - - hex1 = istshift(&scanner); - hex2 = istshift(&scanner); - c = (hex2i(hex1) << 4) + hex2i(hex2); - - if (is_unreserved_character(c)) { - output = __istappend(output, c); - } - else { - output = __istappend(output, current); - output = __istappend(output, hex1); - output = __istappend(output, hex2); - } - - continue; - } - } + if (pct != istptr(scanner)) { + const size_t run = pct - istptr(scanner); - if (strict) { - err = URI_NORMALIZER_ERR_INVALID_INPUT; - goto fail; + memcpy(istend(output), istptr(scanner), run); + output = ist2(istptr(output), istlen(output) + run); + scanner = istadv(scanner, run); + } + + /* scanner now starts on the '%'. */ + istshift(&scanner); + + if (istlen(scanner) >= 2 && ishex(istptr(scanner)[0]) && ishex(istptr(scanner)[1])) { + char hex1, hex2, c; + + hex1 = istshift(&scanner); + hex2 = istshift(&scanner); + c = (hex2i(hex1) << 4) + hex2i(hex2); + + if (is_unreserved_character(c)) { + output = __istappend(output, c); } else { - output = __istappend(output, current); + output = __istappend(output, '%'); + output = __istappend(output, hex1); + output = __istappend(output, hex2); } } + else if (strict) { + err = URI_NORMALIZER_ERR_INVALID_INPUT; + goto fail; + } else { - output = __istappend(output, current); + output = __istappend(output, '%'); } } @@ -171,28 +183,39 @@ enum uri_normalizer_err uri_normalizer_percent_upper(const struct ist input, int } while (istlen(scanner)) { - const char current = istshift(&scanner); + /* Bulk-copy the run before the next '%', see + * uri_normalizer_percent_decode_unreserved(). + */ + const char *pct = memchr(istptr(scanner), '%', istlen(scanner)); + + if (!pct) { + memcpy(istend(output), istptr(scanner), istlen(scanner)); + output = ist2(istptr(output), istlen(output) + istlen(scanner)); + break; + } - if (current == '%') { - if (istlen(scanner) >= 2) { - if (ishex(istptr(scanner)[0]) && ishex(istptr(scanner)[1])) { - output = __istappend(output, current); - output = __istappend(output, toupper((unsigned char)istshift(&scanner))); - output = __istappend(output, toupper((unsigned char)istshift(&scanner))); - continue; - } - } + if (pct != istptr(scanner)) { + const size_t run = pct - istptr(scanner); - if (strict) { - err = URI_NORMALIZER_ERR_INVALID_INPUT; - goto fail; - } - else { - output = __istappend(output, current); - } + memcpy(istend(output), istptr(scanner), run); + output = ist2(istptr(output), istlen(output) + run); + scanner = istadv(scanner, run); + } + + /* scanner now starts on the '%'. */ + istshift(&scanner); + + if (istlen(scanner) >= 2 && ishex(istptr(scanner)[0]) && ishex(istptr(scanner)[1])) { + output = __istappend(output, '%'); + output = __istappend(output, toupper((unsigned char)istshift(&scanner))); + output = __istappend(output, toupper((unsigned char)istshift(&scanner))); + } + else if (strict) { + err = URI_NORMALIZER_ERR_INVALID_INPUT; + goto fail; } else { - output = __istappend(output, current); + output = __istappend(output, '%'); } } -- 2.45.1

