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.

When the scanner already sits on a '%', which is the case right after
every escape in UTF-8 text since each of its bytes is encoded, memchr()
is not called at all and the escape is handled directly, so memchr()
only ever has to find the next non-adjacent '%'. Each lookup skipped
saves 2 to 4 ns on an AMD EPYC 7413 with gcc 8.5.0 -O2: with a 5 kB
path made only of lowercase unreserved escapes (%61%62...) the CPU time
per request goes from 616.1 us to 609.2 us (-1.1%), and with a 2 kB
path of uppercase UTF-8 escapes, where the output equals the input,
from 575.3 us to 571.0 us (-0.8%). An 8 byte path does not change
measurably.

The gain of this series on long query strings comes almost entirely
from the next patch, which is where those numbers are quoted.

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 | 108 +++++++++++++++++++++++++------------------
 1 file changed, 63 insertions(+), 45 deletions(-)

diff --git a/src/uri_normalizer.c b/src/uri_normalizer.c
index 12317f1..7c5f880 100644
--- a/src/uri_normalizer.c
+++ b/src/uri_normalizer.c
@@ -102,40 +102,51 @@ 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. When the scanner is not on a '%', locate the
+                * next one with memchr() and copy the run before it in one go.
+                * When it is, which is the case right after an escape whenever
+                * the text is UTF-8 (every byte of it is encoded), skip the
+                * lookup and handle the escape directly. The output is the same
+                * as with the byte-at-a-time loop.
+                */
+               if (*istptr(scanner) != '%') {
+                       const char *pct = memchr(istptr(scanner), '%', 
istlen(scanner));
+                       const size_t run = pct ? (size_t)(pct - 
istptr(scanner)) : istlen(scanner);
+
+                       memcpy(istend(output), istptr(scanner), run);
+                       output = ist2(istptr(output), istlen(output) + run);
+                       scanner = istadv(scanner, run);
+
+                       if (!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;
-                               }
-                       }
+               /* scanner now starts on the '%'. */
+               istshift(&scanner);
 
-                       if (strict) {
-                               err = URI_NORMALIZER_ERR_INVALID_INPUT;
-                               goto fail;
+               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 +182,35 @@ 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 '%' and handle adjacent
+                * escapes directly, see 
uri_normalizer_percent_decode_unreserved().
+                */
+               if (*istptr(scanner) != '%') {
+                       const char *pct = memchr(istptr(scanner), '%', 
istlen(scanner));
+                       const size_t run = pct ? (size_t)(pct - 
istptr(scanner)) : istlen(scanner);
+
+                       memcpy(istend(output), istptr(scanner), run);
+                       output = ist2(istptr(output), istlen(output) + run);
+                       scanner = istadv(scanner, run);
+
+                       if (!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;
-                               }
-                       }
+               /* scanner now starts on the '%'. */
+               istshift(&scanner);
 
-                       if (strict) {
-                               err = URI_NORMALIZER_ERR_INVALID_INPUT;
-                               goto fail;
-                       }
-                       else {
-                               output = __istappend(output, current);
-                       }
+               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



Reply via email to