diff --git a/benches/Cargo.toml b/benches/Cargo.toml index 0d881f52..873515a8 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -4,6 +4,10 @@ version = "0.0.0" edition = "2018" publish = false +# `benches` is excluded from the root workspace, so declare an empty workspace +# table to let it build as a standalone package. +[workspace] + [dependencies] bytes = "1" fnv = "1.0.5" @@ -16,6 +20,7 @@ seahash = "3.0.5" serde = "1.0" serde_json = "1.0" criterion = "0.3.2" +gungraun = "0.19.4" [[bench]] name = "header_map" @@ -41,3 +46,39 @@ path = "src/method.rs" [[bench]] name = "uri" path = "src/uri.rs" + +# Gungraun (callgrind-based) mirrors of the benchmarks above. +[[bench]] +name = "gungraun_header_map" +path = "src/gungraun/header_map.rs" +harness = false + +[[bench]] +name = "gungraun_header_name" +path = "src/gungraun/header_name.rs" +harness = false + +[[bench]] +name = "gungraun_header_name2" +path = "src/gungraun/header_name2.rs" +harness = false + +[[bench]] +name = "gungraun_header_value" +path = "src/gungraun/header_value.rs" +harness = false + +[[bench]] +name = "gungraun_method" +path = "src/gungraun/method.rs" +harness = false + +[[bench]] +name = "gungraun_uri" +path = "src/gungraun/uri.rs" +harness = false + +[[bench]] +name = "opt_paths" +path = "src/opt_paths.rs" +harness = false diff --git a/benches/src/gungraun/header_map.rs b/benches/src/gungraun/header_map.rs new file mode 100644 index 00000000..e5abc78c --- /dev/null +++ b/benches/src/gungraun/header_map.rs @@ -0,0 +1,405 @@ +use std::hint::black_box; + +use gungraun::{library_benchmark, library_benchmark_group, main}; +use http::header::*; + +#[library_benchmark] +fn new_insert_get_host() { + let mut h = HeaderMap::default(); + h.insert(HOST, "hyper.rs"); + black_box(h.get(&HOST)); +} + +#[library_benchmark] +fn insert_4_std_get_30() { + let mut h = HeaderMap::default(); + + for i in 0..4 { + h.insert(STD[i].clone(), "foo"); + } + + for i in 0..30 { + black_box(h.get(&STD[i % 4])); + } +} + +#[library_benchmark] +fn insert_6_std_get_6() { + let mut h = HeaderMap::default(); + + for i in 0..6 { + h.insert(STD[i].clone(), "foo"); + } + + for i in 0..6 { + black_box(h.get(&STD[i % 4])); + } +} + +#[library_benchmark] +#[bench::n8(8)] +#[bench::n10(10)] +#[bench::n20(20)] +fn set_n_get_1_std(n: usize) { + let mut h = HeaderMap::default(); + + for hdr in &STD[0..n] { + h.insert(hdr.clone(), "foo"); + } + + black_box(h.get(&STD[0])); +} + +#[library_benchmark] +#[bench::short(args = (10,), setup = custom_hdr)] +#[bench::med(args = (10,), setup = med_custom_hdr)] +#[bench::long(args = (10,), setup = long_custom_hdr)] +#[bench::very_long(args = (10,), setup = very_long_custom_hdr)] +fn set_10_get_1_custom(hdrs: Vec) { + let mut h = HeaderMap::default(); + + for hdr in &hdrs { + h.insert(hdr.clone(), "foo"); + } + + black_box(h.get(&hdrs[0])); +} + +#[library_benchmark] +#[bench::short(args = (20,), setup = custom_hdr)] +#[bench::med(args = (20,), setup = med_custom_hdr)] +#[bench::long(args = (20,), setup = long_custom_hdr)] +#[bench::very_long(args = (20,), setup = very_long_custom_hdr)] +fn set_20_get_1_custom(hdrs: Vec) { + let mut h = HeaderMap::default(); + + for hdr in &hdrs { + h.insert(hdr.clone(), "foo"); + } + + black_box(h.get(&hdrs[0])); +} + +#[library_benchmark] +fn insert_all_std_headers() { + let mut h = HeaderMap::default(); + + for hdr in STD { + black_box(h.insert(hdr.clone(), "foo")); + } +} + +#[library_benchmark] +#[bench::custom_std_79(args = (79,), setup = custom_std)] +fn insert_custom_std_headers(hdrs: Vec) { + let mut h = HeaderMap::default(); + + for hdr in &hdrs { + h.insert(hdr.clone(), "foo"); + } + + black_box(h); +} + +#[library_benchmark] +#[bench::n100(args = (100,), setup = custom_hdr)] +#[bench::n500(args = (500,), setup = custom_hdr)] +fn insert_custom_headers(hdrs: Vec) { + let mut h = HeaderMap::default(); + + for hdr in &hdrs { + black_box(h.insert(hdr.clone(), "foo")); + } +} + +#[library_benchmark] +#[bench::c15(args = ("abcd-abcd-abcde",), setup = parse_name)] +#[bench::c25(args = ("abcd-abcd-abcd-abcd-abcde",), setup = parse_name)] +#[bench::c50(args = ("abcd-abcd-abcd-abcd-abcd-abcd-abcd-abcd-abcd-abcde",), setup = parse_name)] +#[bench::c100( + args = ("abcd-abcd-abcd-abcd-abcd-abcd-abcd-abcd-abcd-abcdeabcd-abcd-abcd-abcd-abcd-abcd-abcd-abcd-abcd-abcde",), + setup = parse_name +)] +fn insert_one_char_header(hdr: HeaderName) { + let mut h = HeaderMap::default(); + h.insert(hdr.clone(), "hello"); + black_box(h); +} + +#[library_benchmark] +#[bench::run(args = (), setup = std_map_10_30)] +fn get_10_of_20_std(h: HeaderMap) { + for hdr in &STD[10..20] { + black_box(h.get(hdr)); + } +} + +#[library_benchmark] +#[bench::run(args = (), setup = std_map_all)] +fn get_100_std(h: HeaderMap) { + for i in 0..100 { + black_box(h.get(&STD[i % STD.len()])); + } +} + +#[library_benchmark] +#[bench::run(args = (), setup = custom_short_map)] +fn get_10_custom_short((hdrs, h): (Vec, HeaderMap)) { + for hdr in &hdrs[..10] { + black_box(h.get(hdr)); + } +} + +#[library_benchmark] +#[bench::run(args = (), setup = parse_hn_hdrs_8)] +fn hn_hdrs_set_8_get_many(hdrs: Vec<(HeaderName, &'static str)>) { + let mut h = HeaderMap::default(); + + for &(ref name, val) in hdrs.iter() { + h.insert(name.clone(), val); + } + + for _ in 0..15 { + black_box(h.get(&CONTENT_LENGTH)); + black_box(h.get(&VARY)); + } +} + +#[library_benchmark] +#[bench::run(args = (), setup = parse_hn_hdrs_8_miss)] +fn hn_hdrs_set_8_get_miss((hdrs, miss): (Vec<(HeaderName, &'static str)>, HeaderName)) { + let mut h = HeaderMap::default(); + + for &(ref name, val) in hdrs.iter() { + h.insert(name.clone(), val); + } + + black_box(h.get(&CONTENT_LENGTH)); + black_box(h.get(&miss)); +} + +#[library_benchmark] +#[bench::run(args = (), setup = parse_hn_hdrs_11_miss)] +fn hn_hdrs_set_11_get_with_miss((hdrs, miss): (Vec<(HeaderName, &'static str)>, HeaderName)) { + let mut h = HeaderMap::default(); + + for &(ref name, val) in hdrs.iter() { + h.insert(name.clone(), val); + } + + for _ in 0..10 { + black_box(h.get(&CONTENT_LENGTH)); + black_box(h.get(&VARY)); + black_box(h.get(&miss)); + } +} + +fn parse_name(s: &str) -> HeaderName { + s.parse().unwrap() +} + +fn custom_hdr(n: usize) -> Vec { + (0..n) + .map(|i| { + let s = format!("x-custom-{}", i); + s.parse().unwrap() + }) + .collect() +} + +fn med_custom_hdr(n: usize) -> Vec { + (0..n) + .map(|i| { + let s = format!("content-length-{}", i); + s.parse().unwrap() + }) + .collect() +} + +fn long_custom_hdr(n: usize) -> Vec { + (0..n) + .map(|i| { + let s = format!("access-control-allow-headers-{}", i); + s.parse().unwrap() + }) + .collect() +} + +fn very_long_custom_hdr(n: usize) -> Vec { + (0..n) + .map(|i| { + let s = format!("access-control-allow-access-control-allow-headers-{}", i); + s.parse().unwrap() + }) + .collect() +} + +fn custom_std(n: usize) -> Vec { + (0..n) + .map(|i| { + let s = format!("{}-{}", STD[i % STD.len()].as_str(), i); + s.parse().unwrap() + }) + .collect() +} + +fn std_map_10_30() -> HeaderMap { + let mut h = HeaderMap::default(); + for hdr in STD[10..30].iter() { + h.insert(hdr.clone(), hdr.as_str().to_string()); + } + h +} + +fn std_map_all() -> HeaderMap { + let mut h = HeaderMap::default(); + for hdr in STD.iter() { + h.insert(hdr.clone(), hdr.as_str().to_string()); + } + h +} + +fn custom_short_map() -> (Vec, HeaderMap) { + let hdrs = custom_hdr(20); + let mut h = HeaderMap::default(); + for hdr in &hdrs { + h.insert(hdr.clone(), hdr.as_str().to_string()); + } + (hdrs, h) +} + +fn parse_hn_hdrs_8() -> Vec<(HeaderName, &'static str)> { + HN_HDRS[..8] + .iter() + .map(|&(name, val)| (name.parse().unwrap(), val)) + .collect() +} + +fn parse_hn_hdrs_8_miss() -> (Vec<(HeaderName, &'static str)>, HeaderName) { + (parse_hn_hdrs_8(), "x-wat".parse().unwrap()) +} + +fn parse_hn_hdrs_11_miss() -> (Vec<(HeaderName, &'static str)>, HeaderName) { + let hdrs = HN_HDRS + .iter() + .map(|&(name, val)| (name.parse().unwrap(), val)) + .collect(); + (hdrs, "x-wat".parse().unwrap()) +} + +const HN_HDRS: [(&'static str, &'static str); 11] = [ + ("Date", "Fri, 27 Jan 2017 23:00:00 GMT"), + ("Content-Type", "text/html; charset=utf-8"), + ("Transfer-Encoding", "chunked"), + ("Connection", "keep-alive"), + ("Set-Cookie", "__cfduid=dbdfbbe3822b61cb8750ba37d894022151485558000; expires=Sat, 27-Jan-18 23:00:00 GMT; path=/; domain=.ycombinator.com; HttpOnly"), + ("Vary", "Accept-Encoding"), + ("Cache-Control", "private"), + ("X-Frame-Options", "DENY"), + ("Strict-Transport-Security", "max-age=31556900; includeSubDomains"), + ("Server", "cloudflare-nginx"), + ("CF-RAY", "327fd1809f3c1baf-SEA"), +]; + +const STD: &'static [HeaderName] = &[ + ACCEPT, + ACCEPT_CHARSET, + ACCEPT_ENCODING, + ACCEPT_LANGUAGE, + ACCEPT_RANGES, + ACCESS_CONTROL_ALLOW_CREDENTIALS, + ACCESS_CONTROL_ALLOW_HEADERS, + ACCESS_CONTROL_ALLOW_METHODS, + ACCESS_CONTROL_ALLOW_ORIGIN, + ACCESS_CONTROL_EXPOSE_HEADERS, + ACCESS_CONTROL_MAX_AGE, + ACCESS_CONTROL_REQUEST_HEADERS, + ACCESS_CONTROL_REQUEST_METHOD, + AGE, + ALLOW, + ALT_SVC, + AUTHORIZATION, + CACHE_CONTROL, + CACHE_STATUS, + CDN_CACHE_CONTROL, + CONNECTION, + CONTENT_DISPOSITION, + CONTENT_ENCODING, + CONTENT_LANGUAGE, + CONTENT_LENGTH, + CONTENT_LOCATION, + CONTENT_RANGE, + CONTENT_SECURITY_POLICY, + CONTENT_SECURITY_POLICY_REPORT_ONLY, + CONTENT_TYPE, + COOKIE, + DNT, + DATE, + ETAG, + EXPECT, + EXPIRES, + FORWARDED, + FROM, + HOST, + IF_MATCH, + IF_MODIFIED_SINCE, + IF_NONE_MATCH, + IF_RANGE, + IF_UNMODIFIED_SINCE, + LAST_MODIFIED, + LINK, + LOCATION, + MAX_FORWARDS, + ORIGIN, + PRAGMA, + PROXY_AUTHENTICATE, + PROXY_AUTHORIZATION, + PUBLIC_KEY_PINS, + PUBLIC_KEY_PINS_REPORT_ONLY, + RANGE, + REFERER, + REFERRER_POLICY, + REFRESH, + RETRY_AFTER, + SERVER, + SET_COOKIE, + STRICT_TRANSPORT_SECURITY, + TE, + TRAILER, + TRANSFER_ENCODING, + USER_AGENT, + UPGRADE, + UPGRADE_INSECURE_REQUESTS, + VARY, + VIA, + WARNING, + WWW_AUTHENTICATE, + X_CONTENT_TYPE_OPTIONS, + X_DNS_PREFETCH_CONTROL, + X_FRAME_OPTIONS, + X_XSS_PROTECTION, +]; + +library_benchmark_group!( + name = header_map, + benchmarks = [ + new_insert_get_host, + insert_4_std_get_30, + insert_6_std_get_6, + set_n_get_1_std, + set_10_get_1_custom, + set_20_get_1_custom, + insert_all_std_headers, + insert_custom_std_headers, + insert_custom_headers, + insert_one_char_header, + get_10_of_20_std, + get_100_std, + get_10_custom_short, + hn_hdrs_set_8_get_many, + hn_hdrs_set_8_get_miss, + hn_hdrs_set_11_get_with_miss + ] +); + +main!(library_benchmark_groups = header_map); diff --git a/benches/src/gungraun/header_name.rs b/benches/src/gungraun/header_name.rs new file mode 100644 index 00000000..7d4b3b70 --- /dev/null +++ b/benches/src/gungraun/header_name.rs @@ -0,0 +1,292 @@ +use std::hint::black_box; + +use gungraun::{library_benchmark, library_benchmark_group, main}; +use http::header::HeaderName; + +fn make_all_known_headers() -> Vec> { + // Standard request headers + vec![b"A-IM".to_vec(), + b"Accept".to_vec(), + b"Accept-Charset".to_vec(), + b"Accept-Datetime".to_vec(), + b"Accept-Encoding".to_vec(), + b"Accept-Language".to_vec(), + b"Access-Control-Request-Method".to_vec(), + b"Authorization".to_vec(), + b"Cache-Control".to_vec(), + b"Connection".to_vec(), + b"Permanent".to_vec(), + b"Content-Length".to_vec(), + b"Content-MD5".to_vec(), + b"Content-Type".to_vec(), + b"Cookie".to_vec(), + b"Date".to_vec(), + b"Expect".to_vec(), + b"Forwarded".to_vec(), + b"From".to_vec(), + b"Host".to_vec(), + b"Permanent".to_vec(), + b"HTTP2-Settings".to_vec(), + b"If-Match".to_vec(), + b"If-Modified-Since".to_vec(), + b"If-None-Match".to_vec(), + b"If-Range".to_vec(), + b"If-Unmodified-Since".to_vec(), + b"Max-Forwards".to_vec(), + b"Origin".to_vec(), + b"Pragma".to_vec(), + b"Proxy-Authorization".to_vec(), + b"Range".to_vec(), + b"Referer".to_vec(), + b"TE".to_vec(), + b"User-Agent".to_vec(), + b"Upgrade".to_vec(), + b"Via".to_vec(), + b"Warning".to_vec(), + // common_non_standard + b"Upgrade-Insecure-Requests".to_vec(), + b"Upgrade-Insecure-Requests".to_vec(), + b"X-Requested-With".to_vec(), + b"DNT".to_vec(), + b"X-Forwarded-For".to_vec(), + b"X-Forwarded-Host".to_vec(), + b"X-Forwarded-Proto".to_vec(), + b"Front-End-Https".to_vec(), + b"X-Http-Method-Override".to_vec(), + b"X-ATT-DeviceId".to_vec(), + b"X-Wap-Profile".to_vec(), + b"Proxy-Connection".to_vec(), + b"X-UIDH".to_vec(), + b"X-Csrf-Token".to_vec(), + b"X-Request-ID".to_vec(), + b"X-Correlation-ID".to_vec(), + b"Save-Data".to_vec(), + // standard_response_headers + b"Accept-Patch".to_vec(), + b"Accept-Ranges".to_vec(), + b"Access-Control-Allow-Credentials".to_vec(), + b"Access-Control-Allow-Headers".to_vec(), + b"Access-Control-Allow-Methods".to_vec(), + b"Access-Control-Allow-Origin".to_vec(), + b"Access-Control-Expose-Headers".to_vec(), + b"Access-Control-Max-Age".to_vec(), + b"Age".to_vec(), + b"Allow".to_vec(), + b"Alt-Svc".to_vec(), + b"Cache-Control".to_vec(), + b"Connection".to_vec(), + b"Content-Disposition".to_vec(), + b"Content-Encoding".to_vec(), + b"Content-Language".to_vec(), + b"Content-Length".to_vec(), + b"Content-Location".to_vec(), + b"Content-MD5".to_vec(), + b"Content-Range".to_vec(), + b"Content-Type".to_vec(), + b"Date".to_vec(), + b"Delta-Base".to_vec(), + b"ETag".to_vec(), + b"Expires".to_vec(), + b"IM".to_vec(), + b"Last-Modified".to_vec(), + b"Link".to_vec(), + b"Location".to_vec(), + b"P3P".to_vec(), + b"Permanent".to_vec(), + b"Pragma".to_vec(), + b"Proxy-Authenticate".to_vec(), + b"Public-Key-Pins".to_vec(), + b"Retry-After".to_vec(), + b"Server".to_vec(), + b"Set-Cookie".to_vec(), + b"Strict-Transport-Security".to_vec(), + b"Tk".to_vec(), + b"Trailer".to_vec(), + b"Transfer-Encoding".to_vec(), + b"Upgrade".to_vec(), + b"Vary".to_vec(), + b"Via".to_vec(), + b"Warning".to_vec(), + b"WWW-Authenticate".to_vec(), + b"X-Frame-Options".to_vec(), + // common_non_standard_response + b"Content-Security-Policy".to_vec(), + b"Refresh".to_vec(), + b"Status".to_vec(), + b"Timing-Allow-Origin".to_vec(), + b"X-Content-Duration".to_vec(), + b"X-Content-Security-Policy".to_vec(), + b"X-Content-Type-Options".to_vec(), + b"X-Correlation-ID".to_vec(), + b"X-Powered-By".to_vec(), + b"X-Request-ID".to_vec(), + b"X-UA-Compatible".to_vec(), + b"X-WebKit-CSP".to_vec(), + b"X-XSS-Protection".to_vec(), + ] +} + +static ALL_KNOWN_HEADERS: &[&str] = &[ + // Standard request headers + "a-im", + "accept", + "accept-charset", + "accept-datetime", + "accept-encoding", + "accept-language", + "access-control-request-method", + "authorization", + "cache-control", + "connection", + "permanent", + "content-length", + "content-md5", + "content-type", + "cookie", + "date", + "expect", + "forwarded", + "from", + "host", + "permanent", + "http2-settings", + "if-match", + "if-modified-since", + "if-none-match", + "if-range", + "if-unmodified-since", + "max-forwards", + "origin", + "pragma", + "proxy-authorization", + "range", + "referer", + "te", + "user-agent", + "upgrade", + "via", + "warning", + // common_non_standard + "upgrade-insecure-requests", + "upgrade-insecure-requests", + "x-requested-with", + "dnt", + "x-forwarded-for", + "x-forwarded-host", + "x-forwarded-proto", + "front-end-https", + "x-http-method-override", + "x-att-deviceid", + "x-wap-profile", + "proxy-connection", + "x-uidh", + "x-csrf-token", + "x-request-id", + "x-correlation-id", + "save-data", + // standard_response_headers + "accept-patch", + "accept-ranges", + "access-control-allow-credentials", + "access-control-allow-headers", + "access-control-allow-methods", + "access-control-allow-origin", + "access-control-expose-headers", + "access-control-max-age", + "age", + "allow", + "alt-svc", + "cache-control", + "connection", + "content-disposition", + "content-encoding", + "content-language", + "content-length", + "content-location", + "content-md5", + "content-range", + "content-type", + "date", + "delta-base", + "etag", + "expires", + "im", + "last-modified", + "link", + "location", + "p3p", + "permanent", + "pragma", + "proxy-authenticate", + "public-key-pins", + "retry-after", + "server", + "set-cookie", + "strict-transport-security", + "tk", + "trailer", + "transfer-encoding", + "upgrade", + "vary", + "via", + "warning", + "www-authenticate", + "x-frame-options", + // common_non_standard_response + "content-security-policy", + "refresh", + "status", + "timing-allow-origin", + "x-content-duration", + "x-content-security-policy", + "x-content-type-options", + "x-correlation-id", + "x-powered-by", + "x-request-id", + "x-ua-compatible", + "x-webkit-csp", + "x-xss-protection", +]; + +#[library_benchmark] +fn header_name_easy() -> HeaderName { + black_box(HeaderName::from_bytes(black_box(&b"Content-type"[..])).unwrap()) +} + +#[library_benchmark] +fn header_name_custom() -> HeaderName { + black_box(HeaderName::from_bytes(black_box(&b"Foo-Bar-Baz-Blah"[..])).unwrap()) +} + +#[library_benchmark] +fn header_name_bad() { + black_box(HeaderName::from_bytes(black_box(&b"bad header name"[..])).expect_err("Bad header name")); +} + +#[library_benchmark] +#[bench::various(args = (), setup = make_all_known_headers)] +fn header_name_various(all_known_headers: Vec>) { + for name in &all_known_headers { + black_box(HeaderName::from_bytes(black_box(name.as_slice())).unwrap()); + } +} + +#[library_benchmark] +fn header_name_from_static() { + for name in ALL_KNOWN_HEADERS { + black_box(HeaderName::from_static(black_box(name))); + } +} + +library_benchmark_group!( + name = header_name, + benchmarks = [ + header_name_easy, + header_name_custom, + header_name_bad, + header_name_various, + header_name_from_static + ] +); + +main!(library_benchmark_groups = header_name); diff --git a/benches/src/gungraun/header_name2.rs b/benches/src/gungraun/header_name2.rs new file mode 100644 index 00000000..9b4fcac4 --- /dev/null +++ b/benches/src/gungraun/header_name2.rs @@ -0,0 +1,49 @@ +use std::hint::black_box; + +use gungraun::{library_benchmark, library_benchmark_group, main}; +use http::header::HeaderName; + +// This is a list of some of the standard headers ordered by increasing size. +// It has exactly one standard header per size (some sizes don't have a standard +// header). +#[library_benchmark] +#[bench::size_2("te")] +#[bench::size_3("age")] +#[bench::size_4("date")] +#[bench::size_5("allow")] +#[bench::size_6("accept")] +#[bench::size_7("alt-svc")] +#[bench::size_8("if-match")] +#[bench::size_9("forwarded")] +#[bench::size_10("connection")] +#[bench::size_11("retry-after")] +#[bench::size_12("content-type")] +#[bench::size_13("accept-ranges")] +#[bench::size_14("accept-charset")] +#[bench::size_15("accept-encoding")] +#[bench::size_16("content-encoding")] +#[bench::size_17("if-modified-since")] +#[bench::size_18("proxy-authenticate")] +#[bench::size_19("content-disposition")] +#[bench::size_20("sec-websocket-accept")] +#[bench::size_21("sec-websocket-version")] +#[bench::size_22("access-control-max-age")] +#[bench::size_23("content-security-policy")] +#[bench::size_24("sec-websocket-extensions")] +#[bench::size_25("strict-transport-security")] +#[bench::size_27("access-control-allow-origin")] +#[bench::size_28("access-control-allow-headers")] +#[bench::size_29("access-control-expose-headers")] +#[bench::size_30("access-control-request-headers")] +#[bench::size_33("access-control-allow-credentials")] +#[bench::size_36("content-security-policy-report-only")] +fn header_name_by_size(name: &'static str) -> HeaderName { + black_box(HeaderName::from_static(black_box(name))) +} + +library_benchmark_group!( + name = std_hdr, + benchmarks = header_name_by_size +); + +main!(library_benchmark_groups = std_hdr); diff --git a/benches/src/gungraun/header_value.rs b/benches/src/gungraun/header_value.rs new file mode 100644 index 00000000..20f919b8 --- /dev/null +++ b/benches/src/gungraun/header_value.rs @@ -0,0 +1,44 @@ +use std::hint::black_box; + +use bytes::Bytes; +use gungraun::{library_benchmark, library_benchmark_group, main}; +use http::HeaderValue; + +static SHORT: &'static [u8] = b"localhost"; +static LONG: &'static [u8] = b"Mozilla/5.0 (X11; CrOS x86_64 9592.71.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.80 Safari/537.36"; + +fn make_bytes(bytes: &'static [u8]) -> Bytes { + Bytes::from_static(bytes) +} + +fn make_value(bytes: &'static [u8]) -> HeaderValue { + HeaderValue::from_bytes(bytes).unwrap() +} + +#[library_benchmark] +#[bench::short(args = (SHORT,), setup = make_bytes)] +#[bench::long(args = (LONG,), setup = make_bytes)] +fn from_shared(bytes: Bytes) -> HeaderValue { + black_box(HeaderValue::from_maybe_shared(black_box(bytes)).unwrap()) +} + +#[library_benchmark] +#[bench::short(args = (SHORT,), setup = make_bytes)] +#[bench::long(args = (LONG,), setup = make_bytes)] +fn from_shared_unchecked(bytes: Bytes) -> HeaderValue { + black_box(unsafe { HeaderValue::from_maybe_shared_unchecked(black_box(bytes)) }) +} + +#[library_benchmark] +#[bench::short(args = (SHORT,), setup = make_value)] +#[bench::long(args = (LONG,), setup = make_value)] +fn to_str(value: HeaderValue) { + black_box(black_box(&value).to_str().unwrap()); +} + +library_benchmark_group!( + name = header_value, + benchmarks = [from_shared, from_shared_unchecked, to_str] +); + +main!(library_benchmark_groups = header_value); diff --git a/benches/src/gungraun/method.rs b/benches/src/gungraun/method.rs new file mode 100644 index 00000000..51bc5ca1 --- /dev/null +++ b/benches/src/gungraun/method.rs @@ -0,0 +1,40 @@ +use std::hint::black_box; + +use gungraun::{library_benchmark, library_benchmark_group, main}; +use http::method::Method; + +fn make_all_methods() -> Vec> { + vec![ + b"OPTIONS".to_vec(), + b"GET".to_vec(), + b"POST".to_vec(), + b"PUT".to_vec(), + b"DELETE".to_vec(), + b"HEAD".to_vec(), + b"TRACE".to_vec(), + b"CONNECT".to_vec(), + b"PATCH".to_vec(), + b"CUSTOM_SHORT".to_vec(), + b"CUSTOM_LONG_METHOD".to_vec(), + ] +} + +#[library_benchmark] +fn method_easy() -> Method { + black_box(Method::from_bytes(black_box(&b"GET"[..])).unwrap()) +} + +#[library_benchmark] +#[bench::various(args = (), setup = make_all_methods)] +fn method_various(all_methods: Vec>) { + for name in &all_methods { + black_box(Method::from_bytes(black_box(name.as_slice())).unwrap()); + } +} + +library_benchmark_group!( + name = method, + benchmarks = [method_easy, method_various] +); + +main!(library_benchmark_groups = method); diff --git a/benches/src/gungraun/uri.rs b/benches/src/gungraun/uri.rs new file mode 100644 index 00000000..3ed81d23 --- /dev/null +++ b/benches/src/gungraun/uri.rs @@ -0,0 +1,33 @@ +use std::hint::black_box; + +use gungraun::{library_benchmark, library_benchmark_group, main}; +use http::Uri; + +#[library_benchmark] +#[bench::slash("/")] +#[bench::relative_medium("/wp-content/uploads/2010/03/hello-kitty-darth-vader-pink.jpg")] +#[bench::relative_query( + "/wp-content/uploads/2010/03/hello-kitty-darth-vader-pink.jpg?foo={bar}|baz%13%11quux" +)] +fn uri_parse(s: &str) -> Uri { + black_box(black_box(s).parse::().unwrap()) +} + +fn parse(s: &str) -> Uri { + s.parse().unwrap() +} + +#[library_benchmark] +#[bench::relative(args = ("/wp-content/uploads/2010/03/hello-kitty-darth-vader-pink.jpg",), setup = parse)] +#[bench::relative_query(args = ("/wp-content/uploads/2010/03/hello-kitty-darth-vader-pink.jpg?foo=bar&baz=quux",), setup = parse)] +#[bench::absolute(args = ("https://www.example.com/wp-content/uploads/hello.jpg?foo=bar",), setup = parse)] +fn uri_to_string(uri: Uri) -> String { + black_box(black_box(&uri).to_string()) +} + +library_benchmark_group!( + name = uri, + benchmarks = [uri_parse, uri_to_string] +); + +main!(library_benchmark_groups = uri); diff --git a/benches/src/opt_paths.rs b/benches/src/opt_paths.rs new file mode 100644 index 00000000..fc164ac5 --- /dev/null +++ b/benches/src/opt_paths.rs @@ -0,0 +1,68 @@ +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use http::header::*; +use http::{HeaderValue, Uri}; + +static SHORT: &[u8] = b"localhost"; +static LONG: &[u8] = b"Mozilla/5.0 (X11; CrOS x86_64 9592.71.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.80 Safari/537.36"; + +const REL: &str = "/wp-content/uploads/2010/03/hello-kitty-darth-vader-pink.jpg"; +const REL_QUERY: &str = "/wp-content/uploads/2010/03/hello-kitty-darth-vader-pink.jpg?foo=bar&baz=quux"; +const ABS: &str = "https://www.example.com/wp-content/uploads/hello.jpg?foo=bar"; + +const STD: &[HeaderName] = &[ + HOST, CONTENT_TYPE, CONTENT_LENGTH, ACCEPT, ACCEPT_ENCODING, USER_AGENT, + CONNECTION, CACHE_CONTROL, DATE, SERVER, +]; + +fn header_value(c: &mut Criterion) { + c.bench_function("hv_from_bytes_short", |b| { + b.iter(|| HeaderValue::from_bytes(black_box(SHORT)).unwrap()) + }); + c.bench_function("hv_from_bytes_long", |b| { + b.iter(|| HeaderValue::from_bytes(black_box(LONG)).unwrap()) + }); + let short = HeaderValue::from_bytes(SHORT).unwrap(); + let long = HeaderValue::from_bytes(LONG).unwrap(); + c.bench_function("hv_to_str_short", |b| { + b.iter(|| black_box(&short).to_str().unwrap()) + }); + c.bench_function("hv_to_str_long", |b| { + b.iter(|| black_box(&long).to_str().unwrap()) + }); +} + +fn uri(c: &mut Criterion) { + c.bench_function("uri_parse_relative_medium", |b| { + b.iter(|| black_box(REL).parse::().unwrap()) + }); + c.bench_function("uri_parse_relative_query", |b| { + b.iter(|| black_box(REL_QUERY).parse::().unwrap()) + }); + let rel: Uri = REL.parse().unwrap(); + let rel_query: Uri = REL_QUERY.parse().unwrap(); + let abs: Uri = ABS.parse().unwrap(); + c.bench_function("uri_to_string_relative", |b| { + b.iter(|| black_box(&rel).to_string()) + }); + c.bench_function("uri_to_string_relative_query", |b| { + b.iter(|| black_box(&rel_query).to_string()) + }); + c.bench_function("uri_to_string_absolute", |b| { + b.iter(|| black_box(&abs).to_string()) + }); +} + +fn header_map(c: &mut Criterion) { + c.bench_function("hm_insert_10_std", |b| { + b.iter(|| { + let mut m = HeaderMap::default(); + for hdr in STD { + m.insert(hdr.clone(), "foo"); + } + black_box(m) + }) + }); +} + +criterion_group!(benches, header_value, uri, header_map); +criterion_main!(benches); diff --git a/src/header/map.rs b/src/header/map.rs index 58fbaace..6a7628e1 100644 --- a/src/header/map.rs +++ b/src/header/map.rs @@ -3721,6 +3721,7 @@ fn probe_distance(mask: Size, hash: HashValue, current: usize) -> usize { current.wrapping_sub(desired_pos(mask, hash)) & mask as usize } +#[inline] fn hash_elem_using(danger: &Danger, k: &K) -> HashValue where K: Hash + ?Sized, diff --git a/src/header/value.rs b/src/header/value.rs index abd5d036..5a912d9f 100644 --- a/src/header/value.rs +++ b/src/header/value.rs @@ -213,10 +213,13 @@ impl HeaderValue { src: T, into: F, ) -> Result { + // Avoid an early return so the loop vectorizes. + let mut bad = false; for &b in src.as_ref() { - if !is_valid(b) { - return Err(InvalidHeaderValue { _priv: () }); - } + bad |= !is_valid(b); + } + if bad { + return Err(InvalidHeaderValue { _priv: () }); } Ok(HeaderValue { inner: into(src), @@ -240,10 +243,13 @@ impl HeaderValue { pub fn to_str(&self) -> Result<&str, ToStrError> { let bytes = self.as_ref(); + // Avoid an early return so the loop vectorizes. + let mut bad = false; for &b in bytes { - if !is_visible_ascii(b) { - return Err(ToStrError { _priv: () }); - } + bad |= !is_visible_ascii(b); + } + if bad { + return Err(ToStrError { _priv: () }); } unsafe { Ok(str::from_utf8_unchecked(bytes)) } diff --git a/src/uri/mod.rs b/src/uri/mod.rs index 16b45c84..05511dfa 100644 --- a/src/uri/mod.rs +++ b/src/uri/mod.rs @@ -1032,17 +1032,19 @@ impl Default for Uri { impl fmt::Display for Uri { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if let Some(scheme) = self.scheme() { - write!(f, "{}://", scheme)?; + f.write_str(scheme.as_str())?; + f.write_str("://")?; } if let Some(authority) = self.authority() { - write!(f, "{}", authority)?; + f.write_str(authority.as_str())?; } - write!(f, "{}", self.path())?; + f.write_str(self.path())?; if let Some(query) = self.query() { - write!(f, "?{}", query)?; + f.write_str("?")?; + f.write_str(query)?; } Ok(()) diff --git a/src/uri/path.rs b/src/uri/path.rs index 8cedaba8..a9836a33 100644 --- a/src/uri/path.rs +++ b/src/uri/path.rs @@ -278,11 +278,14 @@ impl fmt::Display for PathAndQuery { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { if !self.data.is_empty() { match self.data.as_bytes()[0] { - b'/' | b'*' => write!(fmt, "{}", &self.data[..]), - _ => write!(fmt, "/{}", &self.data[..]), + b'/' | b'*' => fmt.write_str(&self.data), + _ => { + fmt.write_str("/")?; + fmt.write_str(&self.data) + } } } else { - write!(fmt, "/") + fmt.write_str("/") } } } @@ -405,6 +408,62 @@ struct Scanned { is_maybe_not_utf8: bool, } +// Per-byte character classes for the path and query scanners. +const CLASS_VALID: u8 = 0; +const CLASS_QUERY: u8 = 1; +const CLASS_FRAGMENT: u8 = 2; +const CLASS_HIGH: u8 = 3; +const CLASS_INVALID: u8 = 4; + +const fn build_path_map() -> [u8; 256] { + let mut t = [CLASS_INVALID; 256]; + let mut i = 0; + while i < 256 { + // See https://url.spec.whatwg.org/#path-state + t[i] = match i as u8 { + b'?' => CLASS_QUERY, + b'#' => CLASS_FRAGMENT, + + // Bytes that don't need to be percent-encoded in the path. + 0x21 | 0x24..=0x3B | 0x3D | 0x40..=0x5F | 0x61..=0x7A | 0x7C | 0x7E => CLASS_VALID, + + // Potentially utf8, checked later. + 0x80..=0xFF => CLASS_HIGH, + + // Should be percent-encoded, but accepted for parity with clients + // that send them as-is (e.g. JSON embedded in the path). + b'"' | b'{' | b'}' => CLASS_VALID, + + _ => CLASS_INVALID, + }; + i += 1; + } + t +} + +const fn build_query_map() -> [u8; 256] { + let mut t = [CLASS_INVALID; 256]; + let mut i = 0; + while i < 256 { + // See https://url.spec.whatwg.org/#query-state + t[i] = match i as u8 { + b'#' => CLASS_FRAGMENT, + + // Allowed: 0x21 / 0x24 - 0x3B / 0x3D / 0x3F - 0x7E + 0x21 | 0x24..=0x3B | 0x3D | 0x3F..=0x7E => CLASS_VALID, + + 0x80..=0xFF => CLASS_HIGH, + + _ => CLASS_INVALID, + }; + i += 1; + } + t +} + +const PATH_MAP: [u8; 256] = build_path_map(); +const QUERY_MAP: [u8; 256] = build_query_map(); + const fn scan_path_and_query(bytes: &[u8]) -> Result { let mut i = 0; let mut query = NONE; @@ -429,49 +488,21 @@ const fn scan_path_and_query(bytes: &[u8]) -> Result { } while i < bytes.len() { - // See https://url.spec.whatwg.org/#path-state - match bytes[i] { - b'?' => { + match PATH_MAP[bytes[i] as usize] { + CLASS_VALID => {} + CLASS_QUERY => { debug_assert!(query == NONE); query = i as u16; i += 1; break; } - b'#' => { + CLASS_FRAGMENT => { fragment = Some(i as u16); break; } - - // This is the range of bytes that don't need to be - // percent-encoded in the path. If it should have been - // percent-encoded, then error. - #[rustfmt::skip] - 0x21 | - 0x24..=0x3B | - 0x3D | - 0x40..=0x5F | - 0x61..=0x7A | - 0x7C | - 0x7E => {} - - // potentially utf8, might not, should check - 0x80..=0xFF => { + CLASS_HIGH => { is_maybe_not_utf8 = true; } - - // These are code points that are supposed to be - // percent-encoded in the path but there are clients - // out there sending them as is and httparse accepts - // to parse those requests, so they are allowed here - // for parity. - // - // For reference, those are code points that are used - // to send requests with JSON directly embedded in - // the URI path. Yes, those things happen for real. - #[rustfmt::skip] - b'"' | - b'{' | b'}' => {} - _ => return Err(ErrorKind::InvalidUriChar), } i += 1; @@ -480,27 +511,15 @@ const fn scan_path_and_query(bytes: &[u8]) -> Result { // query ... if query != NONE { while i < bytes.len() { - match bytes[i] { - // While queries *should* be percent-encoded, most - // bytes are actually allowed... - // See https://url.spec.whatwg.org/#query-state - // - // Allowed: 0x21 / 0x24 - 0x3B / 0x3D / 0x3F - 0x7E - #[rustfmt::skip] - 0x21 | - 0x24..=0x3B | - 0x3D | - 0x3F..=0x7E => {} - - 0x80..=0xFF => { + match QUERY_MAP[bytes[i] as usize] { + CLASS_VALID => {} + CLASS_HIGH => { is_maybe_not_utf8 = true; } - - b'#' => { + CLASS_FRAGMENT => { fragment = Some(i as u16); break; } - _ => return Err(ErrorKind::InvalidUriChar), } i += 1;