aquatic http protocol: request parsing: remove hashmap for performance

request-from-bytes:

time:   [720.22 ns 723.34 ns 726.73 ns]
change: [-53.265% -52.884% -52.477%] (p = 0.00 < 0.01)

Performance has improved.
This commit is contained in:
Joakim Frostegård 2020-07-21 00:11:03 +02:00
parent 289cc4fcb5
commit 8fea96bcd2
6 changed files with 1057 additions and 1095 deletions

View file

@ -22,7 +22,6 @@
## aquatic_http
* upper limit on request read buffer
* request parsing:
* just skip the hashmap and save data directly into struct?
* smartstring: maybe use for keys? maybe use less? needs benchmarking
* test multiple scrape hashes
* test with strange/bad inputs, with and without quickcheck

View file

@ -1,5 +1,4 @@
use anyhow::Context;
use hashbrown::HashMap;
use smartstring::{SmartString, LazyCompact};
use super::common::*;
@ -169,94 +168,16 @@ impl Request {
let query_string = split_parts.next()
.with_context(|| "no query string")?;
// -- Parse key-value pairs
let mut info_hashes = Vec::new();
let mut opt_peer_id = None;
let mut data = HashMap::new();
let mut opt_port = None;
let mut opt_bytes_left = None;
let mut event = AnnounceEvent::default();
let mut opt_numwant = None;
let mut opt_key = None;
Self::parse_key_value_pairs_memchr(
&mut info_hashes,
&mut opt_peer_id,
&mut data,
query_string
)?;
if location == "/announce" {
let numwant = if let Some(s) = data.remove("numwant"){
let numwant = s.parse::<usize>()
.map_err(|err|
anyhow::anyhow!("parse 'numwant': {}", err)
)?;
Some(numwant)
} else {
None
};
let key = if let Some(s) = data.remove("key"){
if s.len() > 100 {
return Err(anyhow::anyhow!("'key' is too long"))
}
Some(s)
} else {
None
};
let port = if let Some(port) = data.remove("port"){
port.parse().with_context(|| "parse port")?
} else {
return Err(anyhow::anyhow!("no port"));
};
let bytes_left = if let Some(left) = data.remove("left"){
left.parse().with_context(|| "parse bytes left")?
} else {
return Err(anyhow::anyhow!("no left"));
};
let event = if let Some(event) = data.remove("event"){
if let Ok(event) = event.parse(){
event
} else {
return Err(anyhow::anyhow!("invalid event: {}", event));
}
} else {
AnnounceEvent::default()
};
let compact = if let Some(compact) = data.remove("compact"){
if compact.as_str() == "1" {
true
} else {
return Err(anyhow::anyhow!("compact set, but not to 1"));
}
} else {
true
};
let request = AnnounceRequest {
info_hash: info_hashes.pop().with_context(|| "no info_hash")?,
peer_id: opt_peer_id.with_context(|| "no peer_id")?,
port,
bytes_left,
event,
compact,
numwant,
key,
};
Ok(Request::Announce(request))
} else {
let request = ScrapeRequest {
info_hashes,
};
Ok(Request::Scrape(request))
}
}
/// Seems to be somewhat faster than non-memchr version
fn parse_key_value_pairs_memchr<'a>(
info_hashes: &mut Vec<InfoHash>,
opt_peer_id: &mut Option<PeerId>,
data: &mut HashMap<&'a str, SmartString<LazyCompact>>,
query_string: &'a str,
) -> anyhow::Result<()> {
let query_string_bytes = query_string.as_bytes();
let mut ampersand_iter = ::memchr::memchr_iter(b'&', query_string_bytes);
@ -271,7 +192,6 @@ impl Request {
let value = query_string.get(equal_sign_index + 1..segment_end)
.with_context(|| format!("no value at {}..{}", equal_sign_index + 1, segment_end))?;
// whitelist keys to avoid having to use ddos-resistant hashmap
match key {
"info_hash" => {
let value = Self::urldecode_20_bytes(value)?;
@ -281,10 +201,32 @@ impl Request {
"peer_id" => {
let value = Self::urldecode_20_bytes(value)?;
*opt_peer_id = Some(PeerId(value));
opt_peer_id = Some(PeerId(value));
},
"port" | "left" | "event" | "compact" | "numwant" | "key" => {
data.insert(key, value.into());
"port" => {
opt_port = Some(value.parse::<u16>().with_context(|| "parse port")?);
},
"left" => {
opt_bytes_left = Some(value.parse::<usize>().with_context(|| "parse left")?);
},
"event" => {
event = value.parse::<AnnounceEvent>().map_err(|err|
anyhow::anyhow!("invalid event: {}", err)
)?;
},
"compact" => {
if value != "1" {
return Err(anyhow::anyhow!("compact set, but not to 1"));
}
},
"numwant" => {
opt_numwant = Some(value.parse::<usize>().with_context(|| "parse numwant")?);
},
"key" => {
if value.len() > 100 {
return Err(anyhow::anyhow!("'key' is too long"))
}
opt_key = Some(value.into());
},
k => {
::log::info!("ignored unrecognized key: {}", k)
@ -298,7 +240,28 @@ impl Request {
}
}
Ok(())
// -- Put together request
if location == "/announce" {
let request = AnnounceRequest {
info_hash: info_hashes.pop().with_context(|| "no info_hash")?,
peer_id: opt_peer_id.with_context(|| "no peer_id")?,
port: opt_port.with_context(|| "no port")?,
bytes_left: opt_bytes_left.with_context(|| "no left")?,
event,
compact: true,
numwant: opt_numwant,
key: opt_key,
};
Ok(Request::Announce(request))
} else {
let request = ScrapeRequest {
info_hashes,
};
Ok(Request::Scrape(request))
}
}
/// The info hashes and peer id's that are received are url-encoded byte

View file

@ -1 +1 @@
{"mean":{"confidence_interval":{"confidence_level":0.95,"lower_bound":1540.8390649054281,"upper_bound":1558.4887891210565},"point_estimate":1549.4245398703538,"standard_error":4.517692089474024},"median":{"confidence_interval":{"confidence_level":0.95,"lower_bound":1506.8523919746872,"upper_bound":1513.8541497552624},"point_estimate":1510.1168042545828,"standard_error":1.754123550033509},"median_abs_dev":{"confidence_interval":{"confidence_level":0.95,"lower_bound":45.152694401086514,"upper_bound":54.71728947836145},"point_estimate":49.43491905391041,"standard_error":2.470252770404891},"slope":{"confidence_interval":{"confidence_level":0.95,"lower_bound":1528.8205165274087,"upper_bound":1544.1141471464193},"point_estimate":1536.161119110567,"standard_error":3.9201641311296234},"std_dev":{"confidence_interval":{"confidence_level":0.95,"lower_bound":121.9529239151999,"upper_bound":162.2045693733035},"point_estimate":142.20072433991157,"standard_error":10.304640393049143}}
{"mean":{"confidence_interval":{"confidence_level":0.95,"lower_bound":725.4551837276395,"upper_bound":735.0471255185286},"point_estimate":730.0304941357815,"standard_error":2.44315882364003},"median":{"confidence_interval":{"confidence_level":0.95,"lower_bound":710.2692059508443,"upper_bound":713.9114247675105},"point_estimate":711.6918749031568,"standard_error":0.939200067715216},"median_abs_dev":{"confidence_interval":{"confidence_level":0.95,"lower_bound":21.74599816051584,"upper_bound":25.76476135212916},"point_estimate":23.51684998144059,"standard_error":1.0048983533788292},"slope":{"confidence_interval":{"confidence_level":0.95,"lower_bound":720.2235427692906,"upper_bound":726.7290589621539},"point_estimate":723.3388090543597,"standard_error":1.655406933329102},"std_dev":{"confidence_interval":{"confidence_level":0.95,"lower_bound":60.65633946284278,"upper_bound":94.33752282501924},"point_estimate":77.10614491661927,"standard_error":8.653988561946822}}

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
[1272.4120390677972,1377.6532072370742,1658.2963223551465,1763.5374905244237]
[598.8933477988796,648.0355807205904,779.0815351784856,828.2237681001964]