Skip to content

perf(header,uri): faster value validation, URI parse/format, map inserts#852

Open
geeknoid wants to merge 1 commit into
hyperium:masterfrom
geeknoid:master
Open

perf(header,uri): faster value validation, URI parse/format, map inserts#852
geeknoid wants to merge 1 commit into
hyperium:masterfrom
geeknoid:master

Conversation

@geeknoid

@geeknoid geeknoid commented Jul 10, 2026

Copy link
Copy Markdown

Four independent, benchmark-validated micro-optimizations:

  • HeaderValue validation (from_bytes / from_maybe_shared / to_str): replace the early-return byte loop with a branchless OR-accumulation so the scan auto-vectorizes. The valid case scans the whole slice either way; the error path is rare.
  • URI path/query scan: replace the per-byte range match (~10 comparisons per byte) with two compile-time 256-entry classification tables. Stays a const fn (used by from_static); verified to classify all 256 bytes identically to the old match in both the path and query states.
  • URI formatting: Uri and PathAndQuery Display impls use direct write_str calls instead of write!/format-args machinery for their string parts.
  • HeaderMap: mark hash_elem_using #[inline] so it specializes into the insert path.

Adds a gungraun (callgrind, deterministic instruction counts) benchmark suite mirroring the existing benches, plus a criterion wallclock bench (opt_paths) for the affected operations.

Before/after, gungraun instruction counts (deterministic):

  HeaderValue::from_bytes  short          118 ->     98   -16.9%
  HeaderValue::from_bytes  long           950 ->    196   -79.4%
  HeaderValue::to_str      short          175 ->    125   -28.6%
  HeaderValue::to_str      long          1319 ->    217   -83.5%
  Uri parse  relative_medium             1348 ->   1009   -25.1%
  Uri parse  relative_query              1779 ->   1194   -32.9%
  Uri to_string  relative                 553 ->    448   -19.0%
  Uri to_string  relative_query           995 ->    795   -20.1%
  Uri to_string  absolute                1873 ->   1525   -18.6%
  HeaderMap insert_all_std              19914 ->  18850    -5.3%
  HeaderMap set_10_std (n10)             3343 ->   2784   -16.7%
  HeaderMap set_20_std (n20)             6221 ->   5102   -18.0%
  HeaderMap hn_hdrs_set_8_get_miss       3453 ->   3076   -10.9%
  HeaderMap insert_custom (n500)       525783 -> 519283    -1.2%

Before/after, criterion wallclock (median; shared VM, noisy under ~100ns):

  hv_from_bytes_long              89.4 ns ->  33.3 ns   -62.8%
  hv_to_str_long                  89.8 ns ->   9.2 ns   -89.8%
  hv_to_str_short                  7.2 ns ->   3.9 ns   -46.6%
  uri_parse_relative_medium      130.9 ns -> 112.3 ns   -14.2%
  uri_parse_relative_query       196.6 ns -> 177.7 ns    -9.6%
  uri_to_string_relative          34.2 ns ->  26.2 ns   -23.4%
  uri_to_string_relative_query   112.7 ns ->  93.2 ns   -17.3%
  uri_to_string_absolute         193.4 ns -> 171.4 ns   -11.4%
  hm_insert_10_std               300.7 ns -> 296.5 ns    -1.4%

(hv_from_bytes_short is within wallclock noise here at ~28 ns; its gungraun instruction count drops 16.9%.)

Four independent, benchmark-validated micro-optimizations:

* HeaderValue validation (from_bytes / from_maybe_shared / to_str): replace the
  early-return byte loop with a branchless OR-accumulation so the scan
  auto-vectorizes. The valid case scans the whole slice either way; the error
  path is rare.
* URI path/query scan: replace the per-byte range `match` (~10 comparisons per
  byte) with two compile-time 256-entry classification tables. Stays a const fn
  (used by from_static); verified to classify all 256 bytes identically to the
  old match in both the path and query states.
* URI formatting: Uri and PathAndQuery Display impls use direct write_str calls
  instead of write!/format-args machinery for their string parts.
* HeaderMap: mark hash_elem_using #[inline] so it specializes into the insert
  path.

Adds a gungraun (callgrind, deterministic instruction counts) benchmark suite
mirroring the existing benches, plus a criterion wallclock bench (opt_paths) for
the affected operations.

Before/after, gungraun instruction counts (deterministic):

  HeaderValue::from_bytes  short          118 ->     98   -16.9%
  HeaderValue::from_bytes  long           950 ->    196   -79.4%
  HeaderValue::to_str      short          175 ->    125   -28.6%
  HeaderValue::to_str      long          1319 ->    217   -83.5%
  Uri parse  relative_medium             1348 ->   1009   -25.1%
  Uri parse  relative_query              1779 ->   1194   -32.9%
  Uri to_string  relative                 553 ->    448   -19.0%
  Uri to_string  relative_query           995 ->    795   -20.1%
  Uri to_string  absolute                1873 ->   1525   -18.6%
  HeaderMap insert_all_std              19914 ->  18850    -5.3%
  HeaderMap set_10_std (n10)             3343 ->   2784   -16.7%
  HeaderMap set_20_std (n20)             6221 ->   5102   -18.0%
  HeaderMap hn_hdrs_set_8_get_miss       3453 ->   3076   -10.9%
  HeaderMap insert_custom (n500)       525783 -> 519283    -1.2%

Before/after, criterion wallclock (median; shared VM, noisy under ~100ns):

  hv_from_bytes_long              89.4 ns ->  33.3 ns   -62.8%
  hv_to_str_long                  89.8 ns ->   9.2 ns   -89.8%
  hv_to_str_short                  7.2 ns ->   3.9 ns   -46.6%
  uri_parse_relative_medium      130.9 ns -> 112.3 ns   -14.2%
  uri_parse_relative_query       196.6 ns -> 177.7 ns    -9.6%
  uri_to_string_relative          34.2 ns ->  26.2 ns   -23.4%
  uri_to_string_relative_query   112.7 ns ->  93.2 ns   -17.3%
  uri_to_string_absolute         193.4 ns -> 171.4 ns   -11.4%
  hm_insert_10_std               300.7 ns -> 296.5 ns    -1.4%

(hv_from_bytes_short is within wallclock noise here at ~28 ns; its gungraun
instruction count drops 16.9%.)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant