From: Willy Tarreau Date: Wed, 10 Jan 2024 15:34:48 +0000 (+0100) Subject: OPTIM: http: simplify http_get_status_idx() using a hash X-Git-Tag: v3.0-dev2~58 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=19def65228c5b6799037d8ad02a7af91cd7eab65;p=thirdparty%2Fhaproxy.git OPTIM: http: simplify http_get_status_idx() using a hash 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. --- diff --git a/src/http.c b/src/http.c index 9599e0eb58..57750a2a73 100644 --- a/src/http.c +++ b/src/http.c @@ -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.