Last active
December 8, 2023 15:04
-
-
Save bassmanitram/d21157c12d109b73fa23cb086b9b3e03 to your computer and use it in GitHub Desktop.
Infantile comparison between http::HeaderMap and stde::collections::HashMap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[package] | |
name = "test-map" | |
version = "0.1.0" | |
edition = "2021" | |
[features] | |
headermap = [] | |
[dependencies] | |
ahash = "0.8.6" | |
http = "1.0.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// A fairly brute-force way to compare http::HeaderMap<HeaderValue> with | |
// HashMap<HeaderName,Vec<HeaderValue>>, although I am iterating on it to make | |
// it more sophisticated as time goes by. | |
// | |
// It's not a subtle benchmark - just suck in a bunch of headers from a file | |
// then run operations a stupid number of times. If you build the feature "headermap" | |
// you get the http::HeaderMap implementation. Otherwise you get the HashMap implementation. | |
// | |
// The HashMap implementation uses the ahash crate to improve on hashing. | |
// | |
// Where we expect return values I try to use them in the minimal amount of busy work | |
// I can think of to avoid them being optimized out. | |
// | |
// Run: | |
// | |
// cargo run --release 2>/dev/null | |
// cargo run --release --feature headermap 2>/dev/null | |
// | |
// HashMap (with ahash) significantly outguns HeaderMap on every test. | |
// | |
use std::{fs::File, io::{BufRead, BufReader}, time::Instant}; | |
use http::{HeaderName, HeaderValue}; | |
#[cfg(not(feature = "headermap"))] | |
#[cfg(feature = "headermap")] | |
use crate::header_map::HeaderMap; | |
#[cfg(not(feature = "headermap"))] | |
pub mod header_map { | |
use std::collections::HashMap; | |
use ahash::RandomState; | |
use http::{HeaderValue, HeaderName}; | |
pub struct Drain<'a> { | |
m_drain: std::collections::hash_map::Drain<'a, HeaderName, Vec<HeaderValue>>, | |
v_drain: Option<ValueDrain<>>, | |
} | |
pub struct ValueDrain { | |
pub hn: Option<HeaderName>, | |
pub drain: Option<std::vec::IntoIter<HeaderValue>>, | |
} | |
impl ValueDrain { | |
fn new(hn: HeaderName, v: Vec<HeaderValue>) -> Self { | |
Self { | |
hn: Some(hn), | |
drain: Some(v.into_iter()) | |
} | |
} | |
} | |
impl<'a,'b> Iterator for Drain<'a> { | |
type Item = (Option<HeaderName>, HeaderValue); | |
fn next(&mut self) -> Option<Self::Item> { | |
if self.v_drain.is_none() { | |
let Some((hn, v)) = self.m_drain.next() else { return None }; | |
self.v_drain = Some(ValueDrain::new(hn, v)); | |
}; | |
loop { | |
let v_drain: &mut ValueDrain = self.v_drain.as_mut().unwrap(); | |
let Some(value) = v_drain.drain.as_mut().unwrap().next() else { | |
let Some((hn, v)) = self.m_drain.next() else { return None }; | |
self.v_drain = Some(ValueDrain::new(hn, v)); | |
continue; | |
}; | |
return Some(( v_drain.hn.take(), value )); | |
} | |
} | |
} | |
pub struct Values<'a> { | |
m_iter: std::collections::hash_map::Values<'a, HeaderName, Vec<HeaderValue>>, | |
v_iter: Option<std::slice::Iter<'a,HeaderValue>>, | |
} | |
impl<'a> Iterator for Values<'a> { | |
type Item = &'a HeaderValue; | |
fn next(&mut self) -> Option<Self::Item> { | |
if self.v_iter.is_none() { | |
let Some(v) = self.m_iter.next() else { return None }; | |
self.v_iter = Some(v.iter()); | |
}; | |
loop { | |
let v_iter = self.v_iter.as_mut().unwrap(); | |
let Some(value) = v_iter.next() else { | |
let Some(v) = self.m_iter.next() else { return None }; | |
self.v_iter = Some(v.iter()); | |
continue; | |
}; | |
return Some(value); | |
} | |
} | |
} | |
pub struct ValuesMut<'a> { | |
m_iter: std::collections::hash_map::ValuesMut<'a, HeaderName, Vec<HeaderValue>>, | |
v_iter: Option<std::slice::IterMut<'a,HeaderValue>>, | |
} | |
impl<'a> Iterator for ValuesMut<'a> { | |
type Item = &'a mut HeaderValue; | |
fn next(&mut self) -> Option<Self::Item> { | |
if self.v_iter.is_none() { | |
let Some(v) = self.m_iter.next() else { return None }; | |
self.v_iter = Some(v.iter_mut()); | |
}; | |
loop { | |
let v_iter = self.v_iter.as_mut().unwrap(); | |
let Some(value) = v_iter.next() else { | |
let Some(v) = self.m_iter.next() else { return None }; | |
self.v_iter = Some(v.iter_mut()); | |
continue; | |
}; | |
return Some(value); | |
} | |
} | |
} | |
pub struct HeaderMap { | |
m: HashMap::<HeaderName, Vec<HeaderValue>, RandomState>, | |
nv: usize, | |
} | |
impl HeaderMap { | |
pub fn new() -> Self { | |
HeaderMap{m: HashMap::default(), nv: 0} | |
} | |
pub fn with_capacity(capacity: usize) -> Self { | |
HeaderMap{m: HashMap::with_capacity_and_hasher(capacity, RandomState::new()), nv: 0} | |
} | |
pub fn append(&mut self, key: HeaderName, value: HeaderValue) -> bool { | |
self.nv += 1; | |
match self.m.get_mut(&key) { | |
Some(v) => {v.push(value); true}, | |
None => { self.m.insert(key, vec!(value)); false} | |
} | |
} | |
pub fn capacity(&self) -> usize { | |
self.m.capacity() | |
} | |
pub fn clear(&mut self) { | |
self.m.clear(); | |
self.nv = 0; | |
} | |
pub fn contains_key(&mut self, key: &HeaderName) -> bool { | |
self.m.contains_key(key) | |
} | |
pub fn drain<'a,'b>(&'a mut self) -> Drain<'a> | |
where 'b: 'a { | |
self.nv = 0; | |
Drain { m_drain: self.m.drain(), v_drain: None } | |
} | |
pub fn entry<K>(&mut self, _key: &HeaderName) {// -> Entry<'_, T> | |
todo!() | |
} | |
pub fn get(&self, key: &HeaderName) -> Option<&HeaderValue> { | |
self.m.get(key).and_then(|v| if v.is_empty() {None} else {Some(&v[0])}) | |
} | |
pub fn get_all(&self, key: &HeaderName) -> Option<&Vec<HeaderValue>> { | |
self.m.get(key) | |
} | |
pub fn get_mut(&mut self, key: &HeaderName) -> Option<&mut Vec<HeaderValue>> { | |
self.m.get_mut(key) | |
} | |
pub fn insert<K>(&mut self, key: HeaderName, val: HeaderValue) -> Option<HeaderValue> { | |
self.nv += 1; | |
let Some(old_val) = self.m.insert(key, vec!(val)) else { return None; }; | |
self.nv -= old_val.len(); | |
old_val.into_iter().next() | |
} | |
pub fn is_empty(&self) -> bool { | |
self.m.is_empty() | |
} | |
pub fn keys(&self) -> std::collections::hash_map::Keys<'_, HeaderName, Vec<HeaderValue>> { | |
self.m.keys() | |
} | |
pub fn keys_len(&self) -> usize { | |
self.m.len() | |
} | |
pub fn len(&self) -> usize { | |
self.nv | |
} | |
pub fn remove(&mut self, key: &HeaderName) -> Option<HeaderValue> { | |
let Some(v) = self.m.remove(key) else { return None; }; | |
self.nv -= v.len(); | |
v.into_iter().next() | |
} | |
pub fn values(&self) -> Values { | |
Values { m_iter: self.m.values(), v_iter: None } | |
} | |
pub fn values_mut(&mut self) -> ValuesMut { | |
ValuesMut { m_iter: self.m.values_mut(), v_iter: None } | |
} | |
} | |
} | |
#[cfg(feature = "headermap")] | |
mod header_map { | |
use http::{HeaderValue, HeaderName, HeaderMap as HMap}; | |
pub type HeaderMap = HMap::<HeaderValue>; | |
} | |
fn map_it(map: &mut header_map::HeaderMap, ents: &Vec::<(HeaderName, HeaderValue)>) { | |
for (hn, hv) in ents { | |
map.append(hn.clone(), hv.clone()); | |
} | |
} | |
fn find_it(map: &mut header_map::HeaderMap, ents: &Vec::<(HeaderName, HeaderValue)>) -> usize { | |
let mut count: usize = 0; | |
for (hn, _hv) in ents { | |
if let Some(val) = map.get(hn) { | |
// | |
// Use the value to avoid it being optimized out | |
// | |
count += val.len(); | |
} | |
} | |
count | |
} | |
fn contains_key(map: &mut header_map::HeaderMap, ents: &Vec::<(HeaderName, HeaderValue)>) -> usize { | |
let mut count: usize = 0; | |
for (hn, _hv) in ents { | |
// | |
// Use the value to avoid it being optimized out | |
// | |
count += usize::from(map.contains_key(hn)); | |
} | |
count | |
} | |
fn values(map: &mut header_map::HeaderMap, ents: &Vec::<(HeaderName, HeaderValue)>) -> usize { | |
let mut count: usize = 0; | |
for (hn, _hv) in ents { | |
// | |
// Use the value to avoid it being optimized out | |
// | |
count += usize::from(map.values().count()); | |
} | |
count | |
} | |
#[cfg(not(feature = "headermap"))] | |
fn get_all(map: &mut header_map::HeaderMap, ents: &Vec::<(HeaderName, HeaderValue)>) -> usize { | |
let mut count: usize = 0; | |
for (hn, _hv) in ents { | |
if let Some(vals) = map.get_all(hn) { | |
for val in vals.iter() { | |
// | |
// Use each value to avoid it being optimized out | |
// | |
count += val.to_str().unwrap().len(); | |
} | |
} | |
} | |
count | |
} | |
#[cfg(feature = "headermap")] | |
fn get_all(map: &mut header_map::HeaderMap, ents: &Vec::<(HeaderName, HeaderValue)>) -> usize { | |
let mut count: usize = 0; | |
for (hn, _hv) in ents { | |
for val in map.get_all(hn).iter() { | |
// | |
// Use each value to avoid it being optimized out | |
// | |
count += val.to_str().unwrap().len(); | |
} | |
} | |
count | |
} | |
fn lose_it(map: &mut header_map::HeaderMap, ents: &Vec::<(HeaderName, HeaderValue)>) { | |
for (hn, _hv) in ents { | |
map.remove(hn); | |
} | |
} | |
fn main() { | |
let file = File::open("test-data.txt").expect("Could not open file"); | |
let mut ents = Vec::<(HeaderName, HeaderValue)>::new(); | |
for line in BufReader::new(file).lines().map_while(Result::ok) { | |
let Some((key, value)) = line.split_once(':') else { continue }; | |
let hn: HeaderName = HeaderName::from_bytes(key.as_bytes()).expect("Bad header name"); | |
let hv: HeaderValue = HeaderValue::from_str(value).expect("Bad header value"); | |
ents.push((hn,hv)); | |
} | |
let n = 10000; | |
let mut map = header_map::HeaderMap::with_capacity(ents.len()); | |
let start = Instant::now(); | |
for _i in 0..n { | |
map_it(&mut map, &ents) | |
} | |
println!("Append: {}", start.elapsed().as_micros()); | |
let start = Instant::now(); | |
let mut i: usize = 0; | |
for _i in 0..n { | |
i += find_it(&mut map, &ents) | |
} | |
println!("Get: {} ({})", start.elapsed().as_micros(), i); | |
let start = Instant::now(); | |
let mut i: usize = 0; | |
for _i in 0..100 { | |
i += get_all(&mut map, &ents) | |
} | |
println!("Get all: {} ({})", start.elapsed().as_micros(), i); | |
let start = Instant::now(); | |
let mut i: usize = 0; | |
for _i in 0..n { | |
i += contains_key(&mut map, &ents) | |
} | |
println!("Contains: {} ({})", start.elapsed().as_micros(), i); | |
let start = Instant::now(); | |
let mut i: usize = 0; | |
for _i in 0..100 { | |
i += values(&mut map, &ents) | |
} | |
println!("Values: {} ({})", start.elapsed().as_micros(), i); | |
let start = Instant::now(); | |
for _i in 0..n { | |
lose_it(&mut map, &ents) | |
} | |
println!("Remove: {}", start.elapsed().as_micros()); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Content-Length: 233 | |
Content-Type: application/json | |
Accept: */* | |
Accept-Encoding: gzip, deflate, br | |
CloudFront-Forwarded-Proto: https | |
CloudFront-Viewer-Country: ES | |
Host: api.sandbox-113.hub-52.sandbox.assure.dxc.com | |
Postman-Token: 282fd703-3c20-46e9-b1d8-721b53501a64 | |
User-Agent: Amazon CloudFront | |
Via: 1.1 24c7a1aab30c05f7e2022ac9ddfdef7a.cloudfront.net (CloudFront) | |
X-Amz-Cf-Id: 2ioPhGFV_oUEWxEQo0oAc4ATsa3OUz0A3EbVOOSqF5FlLRkTa7recg== | |
X-Amzn-Trace-Id: Root=1-620e1ee7-071242243649b6eb4fc4fdad | |
X-DXC-Behaviour-Prefix: /api | |
X-DXC-Original-Host: sandbox-113.hub-52.sandbox.assure.dxc.com | |
X-DXC-Original-URL: https://sandbox-113.hub-52.sandbox.assure.dxc.com/api/assure-access-management/acls/CSAM_DEV | |
X-Forwarded-For: 213.177.195.117, 64.252.83.195 | |
X-Forwarded-Port: 443 | |
X-Forwarded-Proto: https | |
my-url-header: https://sandbox-113.hub-52.sandbox.assure.dxc.com/api/assure-access-management/acls/CSAM_DEV/martins/url/header | |
Content-Length: 233 | |
Content-Type: application/json | |
Accept: */* | |
Accept-Encoding: gzip, deflate, br | |
CloudFront-Forwarded-Proto: https | |
CloudFront-Viewer-Country: ES | |
Host: api.sandbox-113.hub-52.sandbox.assure.dxc.com | |
Postman-Token: 282fd703-3c20-46e9-b1d8-721b53501a64 | |
User-Agent: Amazon CloudFront | |
Via: 1.1 24c7a1aab30c05f7e2022ac9ddfdef7a.cloudfront.net (CloudFront) | |
X-Amz-Cf-Id: 2ioPhGFV_oUEWxEQo0oAc4ATsa3OUz0A3EbVOOSqF5FlLRkTa7recg== | |
X-Amzn-Trace-Id: Root=1-620e1ee7-071242243649b6eb4fc4fdad | |
X-DXC-Behaviour-Prefix: /api | |
X-DXC-Original-Host: sandbox-113.hub-52.sandbox.assure.dxc.com | |
X-DXC-Original-URL: https://sandbox-113.hub-52.sandbox.assure.dxc.com/api/assure-access-management/acls/CSAM_DEV | |
X-Forwarded-For: 213.177.195.117, 64.252.83.195 | |
X-Forwarded-Port: 443 | |
X-Forwarded-Proto: https |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment