query_param_cmp() called iststop(param, '=') on both of its operands on
every single comparison to find where the parameter name ends. qsort()
performs O(n log n) comparisons, so each parameter name was rescanned
about log2(n) times: for a query string with 427 parameters that is
roughly 7400 scans to recover 427 pieces of information.

The collection loop already walks every parameter once, so locate '='
there and remember the name length alongside the parameter. This turns
the O(n log n) rescanning into a single O(n) pass.

Storing the extra length requires a dedicated struct rather than a bare
struct ist. It is laid out as a pointer plus two 32 bit lengths, which
keeps it at 16 bytes on 64-bit platforms, exactly the size of the
struct ist it replaces, so the number of parameters that fit into the
trash chunk is unchanged and the existing ERR_ALLOC threshold is
preserved there. On 32-bit platforms it is 12 bytes instead of 8, which
reduces that number by a third; queries with more parameters than fit
are rejected with ERR_ALLOC as before, just from a lower bound. Both
lengths are bounded by the buffer size, so 32 bits are always
sufficient.

The resulting order is identical: the same names are compared with the
same istdiff(), and the pointer comparison that makes the sort stable
for identical names is kept.

Measured on a 24-core AMD EPYC 7413 with a 20 kB query string
(427 parameters), gcc -O2, requests handled with keep-alive, comparing
the six normalizers before and after this series:

  8 byte path            266.4 us ->  266.0 us   (no measurable change)
  60 parameter query     314.02 us -> 311.17 us  -0.9%
  427 parameter query   1137.52 us -> 1062.53 us -6.6%

Those are whole-request figures, where normalization is only a part of
the cost. Against a build with the six actions removed the same 427
parameter case costs 989.67 us, so what the actions themselves add goes
from 218.3 us down to 142.9 us, about a third less:

  normalization overhead  +218.3 us -> +142.9 us  -34.6%

The gain grows with the number of parameters, as expected, and short
URIs get essentially nothing out of it. The same series built with -Og
gives -6.2% on the large query, so that gain is not an artefact of one
optimisation level.

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 | 38 +++++++++++++++++++++++++++++---------
 1 file changed, 29 insertions(+), 9 deletions(-)

diff --git a/src/uri_normalizer.c b/src/uri_normalizer.c
index c24d95fd5..cb67d56f0 100644
--- a/src/uri_normalizer.c
+++ b/src/uri_normalizer.c
@@ -394,6 +394,21 @@ enum uri_normalizer_err 
uri_normalizer_path_merge_slashes(const struct ist path,
        return err;
 }
 
+/* A query parameter with the length of its name (the bytes before the
+ * first '=', or the whole parameter if there is none) computed once while
+ * collecting the parameters. Keeping it here spares query_param_cmp() an
+ * O(n log n) rescan for '='. Both lengths are bounded by the buffer size,
+ * so 32 bits are always sufficient. On 64-bit platforms this is the same
+ * 16 bytes as the struct ist it replaces, so the number of parameters
+ * that fit in the trash buffer is unchanged. On 32-bit platforms it is
+ * 12 bytes instead of 8, which reduces that number by a third.
+ */
+struct query_param {
+       const char *ptr;
+       uint32_t len;
+       uint32_t name_len;
+};
+
 /* Compares two query parameters by name. Query parameters are ordered
  * as with memcmp. Shorter parameter names are ordered lower. Identical
  * parameter names are compared by their pointer to maintain a stable
@@ -401,10 +416,10 @@ enum uri_normalizer_err 
uri_normalizer_path_merge_slashes(const struct ist path,
  */
 static int query_param_cmp(const void *a, const void *b)
 {
-       const struct ist param_a = *(struct ist*)a;
-       const struct ist param_b = *(struct ist*)b;
-       const struct ist param_a_name = iststop(param_a, '=');
-       const struct ist param_b_name = iststop(param_b, '=');
+       const struct query_param *param_a = a;
+       const struct query_param *param_b = b;
+       const struct ist param_a_name = ist2(param_a->ptr, param_a->name_len);
+       const struct ist param_b_name = ist2(param_b->ptr, param_b->name_len);
 
        int cmp = istdiff(param_a_name, param_b_name);
 
@@ -412,10 +427,10 @@ static int query_param_cmp(const void *a, const void *b)
                return cmp;
 
        /* The contents are identical: Compare the pointer. */
-       if (istptr(param_a) < istptr(param_b))
+       if (param_a->ptr < param_b->ptr)
                return -1;
 
-       if (istptr(param_a) > istptr(param_b))
+       if (param_a->ptr > param_b->ptr)
                return 1;
 
        return 0;
@@ -432,7 +447,7 @@ enum uri_normalizer_err uri_normalizer_query_sort(const 
struct ist query, const
        struct ist scanner = query;
 
        const struct buffer *trash = get_trash_chunk();
-       struct ist *params = (struct ist *)b_orig(trash);
+       struct query_param *params = (struct query_param *)b_orig(trash);
        const size_t max_param = b_size(trash) / sizeof(*params);
        size_t param_count = 0;
 
@@ -449,13 +464,18 @@ enum uri_normalizer_err uri_normalizer_query_sort(const 
struct ist query, const
 
        while (istlen(scanner) > 0) {
                const struct ist param = istsplit(&scanner, delim);
+               const char *eq;
 
                if (param_count + 1 > max_param) {
                        err = URI_NORMALIZER_ERR_ALLOC;
                        goto fail;
                }
 
-               params[param_count] = param;
+               eq = memchr(istptr(param), '=', istlen(param));
+               params[param_count].ptr = istptr(param);
+               params[param_count].len = istlen(param);
+               params[param_count].name_len = eq ? (uint32_t)(eq - 
istptr(param))
+                                                 : (uint32_t)istlen(param);
                param_count++;
        }
 
@@ -465,7 +485,7 @@ enum uri_normalizer_err uri_normalizer_query_sort(const 
struct ist query, const
                if (i > 0)
                        newquery = __istappend(newquery, delim);
 
-               if (istcat(&newquery, params[i], size) < 0) {
+               if (istcat(&newquery, ist2(params[i].ptr, params[i].len), size) 
< 0) {
                        /* This is impossible, because we checked the size of 
the destination buffer. */
                        my_unreachable();
                        err = URI_NORMALIZER_ERR_INTERNAL_ERROR;
-- 
2.45.1



Reply via email to