]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
OPTIM: http: simplify http_get_status_idx() using a hash
authorWilly Tarreau <w@1wt.eu>
Wed, 10 Jan 2024 15:34:48 +0000 (16:34 +0100)
committerWilly Tarreau <w@1wt.eu>
Thu, 11 Jan 2024 14:10:08 +0000 (15:10 +0100)
This function uses a large switch/case, but the problem is that due to the
numerous holes in the range, the compiler implemented a large jump table.
With a bit of experimentations, some trivial perfect-hash code works, and
since the number of entries is 19, it was enlarged to match the nearest
next power of two to avoid a large modulo operation, and fills the holes
with the default return value (HTTP_ERR_500). Jumping to 32 also results
in a lot of valid keys and allows us to pick small values, resulting in
very fast and compact code. The new function, despite keeping a 32-bytes
table, saves slightly more than 800 bytes of code+data compared to the
previous code, and avoids table jumps that affect the CPU's branch history.

Note that another simple hash worked fine and produced exactly 19 codes
(hence no need to pad holes): ((status * 8675725) >> 13) % 19

But it's still about 24 bytes larger in code to save 13 bytes of data
that are aligned anyway, and it was a bit more expensive so that was
definitely not worth it.

The validity of the table was verified with this test code added just after
it:

  __attribute__((constructor)) void http_hash_test(void)
  {
int i;
for (i = 0; i <= 600; i++)
printf("code %d => %d\n", i, http_get_status_idx(i));
exit(0);
  }

And starting haproxy |grep -vw 14 correctly shows all ordered values
(except 500 of course which is 14).

In case new codes would be added, just play again with dev/phash to
updated the table. As long as there are less than 32 effective entries
it will remain easy to update without having to modify phash.

src/http.c

index 9599e0eb586ab2527ec4b59dcfbde024f3b05244..57750a2a735d77811ec864de0711c2bd8dc813b9 100644 (file)
@@ -368,28 +368,27 @@ enum http_meth_t find_http_meth(const char *str, const int len)
  */
 int http_get_status_idx(unsigned int status)
 {
-       switch (status) {
-       case 200: return HTTP_ERR_200;
-       case 400: return HTTP_ERR_400;
-       case 401: return HTTP_ERR_401;
-       case 403: return HTTP_ERR_403;
-       case 404: return HTTP_ERR_404;
-       case 405: return HTTP_ERR_405;
-       case 407: return HTTP_ERR_407;
-       case 408: return HTTP_ERR_408;
-       case 410: return HTTP_ERR_410;
-       case 413: return HTTP_ERR_413;
-       case 421: return HTTP_ERR_421;
-       case 422: return HTTP_ERR_422;
-       case 425: return HTTP_ERR_425;
-       case 429: return HTTP_ERR_429;
-       case 500: return HTTP_ERR_500;
-       case 501: return HTTP_ERR_501;
-       case 502: return HTTP_ERR_502;
-       case 503: return HTTP_ERR_503;
-       case 504: return HTTP_ERR_504;
-       default: return HTTP_ERR_500;
-       }
+       /* This table was built using dev/phash and easily finds solutions up
+        * to 21 different entries and produces much better code with 32
+        * (padded with err 500 below as it's the default, though only [19] is
+        * the real one).
+        */
+       const uchar codes[32] = {
+               HTTP_ERR_408, HTTP_ERR_200, HTTP_ERR_504, HTTP_ERR_400,
+               HTTP_ERR_500, HTTP_ERR_500, HTTP_ERR_401, HTTP_ERR_410,
+               HTTP_ERR_500, HTTP_ERR_500, HTTP_ERR_500, HTTP_ERR_500,
+               HTTP_ERR_500, HTTP_ERR_429, HTTP_ERR_403, HTTP_ERR_500,
+               HTTP_ERR_421, HTTP_ERR_404, HTTP_ERR_413, HTTP_ERR_500,
+               HTTP_ERR_422, HTTP_ERR_405, HTTP_ERR_500, HTTP_ERR_501,
+               HTTP_ERR_500, HTTP_ERR_500, HTTP_ERR_500, HTTP_ERR_502,
+               HTTP_ERR_407, HTTP_ERR_500, HTTP_ERR_503, HTTP_ERR_425,
+       };
+       uint hash = ((status * 118) >> 5) % 32;
+       uint ret  = codes[hash];
+
+       if (http_err_codes[ret] == status)
+               return ret;
+       return HTTP_ERR_500;
 }
 
 /* This function returns a reason associated with the HTTP status.