aquatic_http: serialize responses to a buffer ref instead of new vec

This improves benchmark performance somewhat and performance
during load testing a bit too.
This commit is contained in:
Joakim Frostegård 2020-07-24 21:58:41 +02:00
parent 228511b3aa
commit fd68a5f603
7 changed files with 1101 additions and 1089 deletions

View file

@ -1,5 +1,5 @@
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use std::io::ErrorKind; use std::io::{ErrorKind, Cursor};
use std::sync::Arc; use std::sync::Arc;
use std::vec::Drain; use std::vec::Drain;
@ -81,6 +81,8 @@ pub fn run_poll_loop(
let mut poll_token_counter = Token(0usize); let mut poll_token_counter = Token(0usize);
let mut iter_counter = 0usize; let mut iter_counter = 0usize;
let mut response_buffer = [0u8; 4096];
let mut response_buffer = Cursor::new(&mut response_buffer[..]);
let mut local_responses = Vec::new(); let mut local_responses = Vec::new();
loop { loop {
@ -113,6 +115,7 @@ pub fn run_poll_loop(
if !(local_responses.is_empty() & (response_channel_receiver.is_empty())) { if !(local_responses.is_empty() & (response_channel_receiver.is_empty())) {
send_responses( send_responses(
&mut response_buffer,
local_responses.drain(..), local_responses.drain(..),
response_channel_receiver.try_iter(), response_channel_receiver.try_iter(),
&mut connections &mut connections
@ -301,6 +304,7 @@ pub fn handle_connection_read_event(
/// Read responses from channel, send to peers /// Read responses from channel, send to peers
pub fn send_responses( pub fn send_responses(
buffer: &mut Cursor<&mut [u8]>,
local_responses: Drain<(ConnectionMeta, Response)>, local_responses: Drain<(ConnectionMeta, Response)>,
channel_responses: crossbeam_channel::TryIter<(ConnectionMeta, Response)>, channel_responses: crossbeam_channel::TryIter<(ConnectionMeta, Response)>,
connections: &mut ConnectionMap, connections: &mut ConnectionMap,
@ -315,7 +319,11 @@ pub fn send_responses(
continue; continue;
} }
match established.send_response(&response.to_bytes()){ buffer.set_position(0);
let bytes_written = response.write(buffer).unwrap();
match established.send_response(&buffer.get_mut()[..bytes_written]){
Ok(()) => { Ok(()) => {
debug!("sent response"); debug!("sent response");
}, },

View file

@ -26,9 +26,14 @@ pub fn bench(c: &mut Criterion) {
let response = Response::Announce(announce_response); let response = Response::Announce(announce_response);
c.bench_function("announce-response-to-bytes", |b| b.iter(|| let mut buffer = [0u8; 4096];
Response::to_bytes(black_box(&response)) let mut buffer = ::std::io::Cursor::new(&mut buffer[..]);
));
c.bench_function("announce-response-to-bytes", |b| b.iter(|| {
buffer.set_position(0);
Response::write(black_box(&response), black_box(&mut buffer)).unwrap();
}));
} }
criterion_group!{ criterion_group!{

View file

@ -1,4 +1,5 @@
use std::net::{Ipv4Addr, Ipv6Addr}; use std::net::{Ipv4Addr, Ipv6Addr};
use std::io::Write;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use serde::Serialize; use serde::Serialize;
@ -50,51 +51,50 @@ pub struct AnnounceResponse {
impl AnnounceResponse { impl AnnounceResponse {
fn to_bytes(&self) -> Vec<u8> { fn write<W: Write>(&self, output: &mut W) -> ::std::io::Result<usize> {
let peers_bytes_len = self.peers.0.len() * 6; let mut bytes_written = 0usize;
let peers6_bytes_len = self.peers6.0.len() * 18;
let mut bytes = Vec::with_capacity( bytes_written += output.write(b"d8:completei")?;
12 + bytes_written += output.write(
5 + // Upper estimate itoa::Buffer::new().format(self.complete).as_bytes()
15 + )?;
5 + // Upper estimate
12 +
5 + // Upper estimate
8 +
peers_bytes_len +
8 +
peers6_bytes_len +
1
);
bytes.extend_from_slice(b"d8:completei"); bytes_written += output.write(b"e10:incompletei")?;
let _ = itoa::write(&mut bytes, self.complete); bytes_written += output.write(
itoa::Buffer::new().format(self.incomplete).as_bytes()
)?;
bytes.extend_from_slice(b"e10:incompletei"); bytes_written += output.write(b"e8:intervali")?;
let _ = itoa::write(&mut bytes, self.incomplete); bytes_written += output.write(
itoa::Buffer::new().format(self.announce_interval).as_bytes()
)?;
bytes.extend_from_slice(b"e8:intervali"); bytes_written += output.write(b"e5:peers")?;
let _ = itoa::write(&mut bytes, self.announce_interval); bytes_written += output.write(
itoa::Buffer::new().format(self.peers.0.len() * 6).as_bytes()
bytes.extend_from_slice(b"e5:peers"); )?;
let _ = itoa::write(&mut bytes, peers_bytes_len); bytes_written += output.write(b":")?;
bytes.push(b':');
for peer in self.peers.0.iter() { for peer in self.peers.0.iter() {
bytes.extend_from_slice(&u32::from(peer.ip_address).to_be_bytes()); bytes_written += output.write(
bytes.extend_from_slice(&peer.port.to_be_bytes()) &u32::from(peer.ip_address).to_be_bytes()
)?;
bytes_written += output.write(&peer.port.to_be_bytes())?;
} }
bytes.extend_from_slice(b"6:peers6"); bytes_written += output.write(b"6:peers6")?;
let _ = itoa::write(&mut bytes, peers6_bytes_len); bytes_written += output.write(
bytes.push(b':'); itoa::Buffer::new().format(self.peers6.0.len() * 18).as_bytes()
)?;
bytes_written += output.write(b":")?;
for peer in self.peers6.0.iter() { for peer in self.peers6.0.iter() {
bytes.extend_from_slice(&u128::from(peer.ip_address).to_be_bytes()); bytes_written += output.write(
bytes.extend_from_slice(&peer.port.to_be_bytes()) &u128::from(peer.ip_address).to_be_bytes()
)?;
bytes_written += output.write(&peer.port.to_be_bytes())?;
} }
bytes.push(b'e'); bytes_written += output.write(b"e")?;
bytes Ok(bytes_written)
} }
} }
@ -107,36 +107,28 @@ pub struct ScrapeResponse {
impl ScrapeResponse { impl ScrapeResponse {
fn to_bytes(&self) -> Vec<u8> { fn write<W: Write>(&self, output: &mut W) -> ::std::io::Result<usize> {
let mut bytes = Vec::with_capacity( let mut bytes_written = 0usize;
9 +
self.files.len() * (
3 +
20 +
12 +
5 + // Upper estimate
31 +
5 + // Upper estimate
2
) +
2
);
bytes.extend_from_slice(b"d5:filesd"); bytes_written += output.write(b"d5:filesd")?;
for (info_hash, statistics) in self.files.iter(){ for (info_hash, statistics) in self.files.iter(){
bytes.extend_from_slice(b"20:"); bytes_written += output.write(b"20:")?;
bytes.extend_from_slice(&info_hash.0); bytes_written += output.write(&info_hash.0)?;
bytes.extend_from_slice(b"d8:completei"); bytes_written += output.write(b"d8:completei")?;
let _ = itoa::write(&mut bytes, statistics.complete); bytes_written += output.write(
bytes.extend_from_slice(b"e10:downloadedi0e10:incompletei"); itoa::Buffer::new().format(statistics.complete).as_bytes()
let _ = itoa::write(&mut bytes, statistics.incomplete); )?;
bytes.extend_from_slice(b"ee"); bytes_written += output.write(b"e10:downloadedi0e10:incompletei")?;
bytes_written += output.write(
itoa::Buffer::new().format(statistics.incomplete).as_bytes()
)?;
bytes_written += output.write(b"ee")?;
} }
bytes.extend_from_slice(b"ee"); bytes_written += output.write(b"ee")?;
bytes Ok(bytes_written)
} }
} }
@ -148,24 +140,20 @@ pub struct FailureResponse {
impl FailureResponse { impl FailureResponse {
fn to_bytes(&self) -> Vec<u8> { fn write<W: Write>(&self, output: &mut W) -> ::std::io::Result<usize> {
let mut bytes_written = 0usize;
let reason_bytes = self.failure_reason.as_bytes(); let reason_bytes = self.failure_reason.as_bytes();
let mut bytes = Vec::with_capacity( bytes_written += output.write(b"d14:failure_reason")?;
18 + bytes_written += output.write(
3 + // Upper estimate itoa::Buffer::new().format(reason_bytes.len()).as_bytes()
1 + )?;
reason_bytes.len() + bytes_written += output.write(b":")?;
1 bytes_written += output.write(reason_bytes)?;
); bytes_written += output.write(b"e")?;
bytes.extend_from_slice(b"d14:failure_reason"); Ok(bytes_written)
let _ = itoa::write(&mut bytes, reason_bytes.len());
bytes.push(b':');
bytes.extend_from_slice(reason_bytes);
bytes.push(b'e');
bytes
} }
} }
@ -180,11 +168,11 @@ pub enum Response {
impl Response { impl Response {
pub fn to_bytes(&self) -> Vec<u8> { pub fn write<W: Write>(&self, output: &mut W) -> ::std::io::Result<usize> {
match self { match self {
Response::Announce(r) => r.to_bytes(), Response::Announce(r) => r.write(output),
Response::Failure(r) => r.to_bytes(), Response::Failure(r) => r.write(output),
Response::Scrape(r) => r.to_bytes(), Response::Scrape(r) => r.write(output),
} }
} }
} }
@ -286,7 +274,11 @@ mod tests {
&Response::Announce(response.clone()) &Response::Announce(response.clone())
).unwrap(); ).unwrap();
response.to_bytes() == reference let mut output = Vec::new();
response.write(&mut output).unwrap();
output == reference
} }
#[quickcheck] #[quickcheck]
@ -294,7 +286,10 @@ mod tests {
let reference = bendy::serde::to_bytes( let reference = bendy::serde::to_bytes(
&Response::Scrape(response.clone()) &Response::Scrape(response.clone())
).unwrap(); ).unwrap();
let hand_written = response.to_bytes();
let mut hand_written = Vec::new();
response.write(&mut hand_written).unwrap();
let success = hand_written == reference; let success = hand_written == reference;
@ -312,6 +307,10 @@ mod tests {
&Response::Failure(response.clone()) &Response::Failure(response.clone())
).unwrap(); ).unwrap();
response.to_bytes() == reference let mut output = Vec::new();
response.write(&mut output).unwrap();
output == reference
} }
} }

View file

@ -1 +1 @@
{"mean":{"confidence_interval":{"confidence_level":0.95,"lower_bound":679.6199835027244,"upper_bound":683.1765435179024},"point_estimate":681.3262892361256,"standard_error":0.9087088681117722},"median":{"confidence_interval":{"confidence_level":0.95,"lower_bound":675.1429773039165,"upper_bound":678.0182057889292},"point_estimate":676.3105408394639,"standard_error":0.6854649236080195},"median_abs_dev":{"confidence_interval":{"confidence_level":0.95,"lower_bound":22.428283556649202,"upper_bound":25.203260654300284},"point_estimate":24.35095483756527,"standard_error":0.69150074330993},"slope":{"confidence_interval":{"confidence_level":0.95,"lower_bound":672.118472824725,"upper_bound":677.6479220619163},"point_estimate":674.6741040360789,"standard_error":1.4165525766541145},"std_dev":{"confidence_interval":{"confidence_level":0.95,"lower_bound":23.51653893971022,"upper_bound":33.64225930231783},"point_estimate":28.565599471881452,"standard_error":2.588927502189203}} {"mean":{"confidence_interval":{"confidence_level":0.95,"lower_bound":658.5830856810967,"upper_bound":662.6140140431078},"point_estimate":660.5315794423136,"standard_error":1.0267544650468448},"median":{"confidence_interval":{"confidence_level":0.95,"lower_bound":645.305767448625,"upper_bound":648.8067825908649},"point_estimate":646.7621214660466,"standard_error":0.965885560495337},"median_abs_dev":{"confidence_interval":{"confidence_level":0.95,"lower_bound":8.471955633936192,"upper_bound":13.107607852360173},"point_estimate":10.494262498769816,"standard_error":1.2403366025964986},"slope":{"confidence_interval":{"confidence_level":0.95,"lower_bound":657.1689080616346,"upper_bound":661.4062067913236},"point_estimate":659.2109887653004,"standard_error":1.0812556505845492},"std_dev":{"confidence_interval":{"confidence_level":0.95,"lower_bound":26.296406154649638,"upper_bound":39.21798204128364},"point_estimate":32.5892125764255,"standard_error":3.3046616744256077}}

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
[560.1173877793271,609.6904444532156,741.8852622502513,791.4583189241397] [555.2496610557599,598.3305180332976,713.2128033067314,756.2936602842691]