perf(h1): optimize for when Body is only 1 chunk

- When the `Body` is created from a buffer of bytes (such as
  `Body::from("hello")`), we can skip some bookkeeping that is
  normally required for streaming bodies.
- Orthogonally, optimize encoding body chunks when the strategy
  is to flatten into the headers buf, by skipping the EncodedBuf
  enum.
This commit is contained in:
Sean McArthur
2018-05-31 17:42:55 -07:00
parent 89c5643713
commit 898e919504
7 changed files with 207 additions and 83 deletions

View File

@@ -5,6 +5,7 @@ use bytes::buf::{Chain, Take};
use iovec::IoVec;
use common::StaticBuf;
use super::io::WriteBuf;
/// Encoders to handle different Transfer-Encodings.
#[derive(Debug, Clone, PartialEq)]
@@ -126,7 +127,7 @@ impl Encoder {
}
}
pub fn encode_and_end<B>(&self, msg: B) -> (EncodedBuf<B::Buf>, bool)
pub(super) fn encode_and_end<B>(&self, msg: B, dst: &mut WriteBuf<EncodedBuf<B::Buf>>) -> bool
where
B: IntoBuf,
{
@@ -134,13 +135,14 @@ impl Encoder {
let len = msg.remaining();
debug_assert!(len > 0, "encode() called with empty buf");
let (kind, eof) = match self.kind {
match self.kind {
Kind::Chunked => {
trace!("encoding chunked {}B", len);
let buf = ChunkSize::new(len)
.chain(msg)
.chain(StaticBuf(b"\r\n0\r\n\r\n"));
(BufKind::Chunked(buf), !self.is_last)
dst.buffer(buf);
!self.is_last
},
Kind::Length(remaining) => {
use std::cmp::Ordering;
@@ -148,25 +150,56 @@ impl Encoder {
trace!("sized write, len = {}", len);
match (len as u64).cmp(&remaining) {
Ordering::Equal => {
(BufKind::Exact(msg), !self.is_last)
dst.buffer(msg);
!self.is_last
},
Ordering::Greater => {
(BufKind::Limited(msg.take(remaining as usize)), !self.is_last)
dst.buffer(msg.take(remaining as usize));
!self.is_last
},
Ordering::Less => {
(BufKind::Exact(msg), false)
dst.buffer(msg);
false
}
}
},
Kind::CloseDelimited => {
trace!("close delimited write {}B", len);
(BufKind::Exact(msg), false)
dst.buffer(msg);
false
}
};
}
}
(EncodedBuf {
kind,
}, eof)
/// Encodes the full body, without verifying the remaining length matches.
///
/// This is used in conjunction with Payload::__hyper_full_data(), which
/// means we can trust that the buf has the correct size (the buf itself
/// was checked to make the headers).
pub(super) fn danger_full_buf<B>(self, msg: B, dst: &mut WriteBuf<EncodedBuf<B::Buf>>)
where
B: IntoBuf,
{
let msg = msg.into_buf();
debug_assert!(msg.remaining() > 0, "encode() called with empty buf");
debug_assert!(match self.kind {
Kind::Length(len) => len == msg.remaining() as u64,
_ => true,
}, "danger_full_buf length mismatches");
match self.kind {
Kind::Chunked => {
let len = msg.remaining();
trace!("encoding chunked {}B", len);
let buf = ChunkSize::new(len)
.chain(msg)
.chain(StaticBuf(b"\r\n0\r\n\r\n"));
dst.buffer(buf);
},
_ => {
dst.buffer(msg);
},
}
}
}
@@ -283,6 +316,30 @@ impl fmt::Write for ChunkSize {
}
}
impl<B: Buf> From<B> for EncodedBuf<B> {
fn from(buf: B) -> Self {
EncodedBuf {
kind: BufKind::Exact(buf),
}
}
}
impl<B: Buf> From<Take<B>> for EncodedBuf<B> {
fn from(buf: Take<B>) -> Self {
EncodedBuf {
kind: BufKind::Limited(buf),
}
}
}
impl<B: Buf> From<Chain<Chain<ChunkSize, B>, StaticBuf>> for EncodedBuf<B> {
fn from(buf: Chain<Chain<ChunkSize, B>, StaticBuf>) -> Self {
EncodedBuf {
kind: BufKind::Chunked(buf),
}
}
}
#[cfg(test)]
mod tests {
use bytes::{BufMut};