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:
		| @@ -388,7 +388,35 @@ where I: AsyncRead + AsyncWrite, | ||||
|         self.io.can_buffer() | ||||
|     } | ||||
|  | ||||
|     pub fn write_head(&mut self, mut head: MessageHead<T::Outgoing>, body: Option<BodyLength>) { | ||||
|     pub fn write_head(&mut self, head: MessageHead<T::Outgoing>, body: Option<BodyLength>) { | ||||
|         if let Some(encoder) = self.encode_head(head, body) { | ||||
|             self.state.writing = if !encoder.is_eof() { | ||||
|                 Writing::Body(encoder) | ||||
|             } else if encoder.is_last() { | ||||
|                 Writing::Closed | ||||
|             } else { | ||||
|                 Writing::KeepAlive | ||||
|             }; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn write_full_msg(&mut self, head: MessageHead<T::Outgoing>, body: B) { | ||||
|         if let Some(encoder) = self.encode_head(head, Some(BodyLength::Known(body.remaining() as u64))) { | ||||
|             let is_last = encoder.is_last(); | ||||
|             // Make sure we don't write a body if we weren't actually allowed | ||||
|             // to do so, like because its a HEAD request. | ||||
|             if !encoder.is_eof() { | ||||
|                 encoder.danger_full_buf(body, self.io.write_buf()); | ||||
|             } | ||||
|             self.state.writing = if is_last { | ||||
|                 Writing::Closed | ||||
|             } else { | ||||
|                 Writing::KeepAlive | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn encode_head(&mut self, mut head: MessageHead<T::Outgoing>, body: Option<BodyLength>) -> Option<Encoder> { | ||||
|         debug_assert!(self.can_write_head()); | ||||
|  | ||||
|         if !T::should_read_first() { | ||||
| @@ -398,7 +426,7 @@ where I: AsyncRead + AsyncWrite, | ||||
|         self.enforce_version(&mut head); | ||||
|  | ||||
|         let buf = self.io.headers_buf(); | ||||
|         self.state.writing = match T::encode(Encode { | ||||
|         match T::encode(Encode { | ||||
|             head: &mut head, | ||||
|             body, | ||||
|             keep_alive: self.state.wants_keep_alive(), | ||||
| @@ -409,19 +437,14 @@ where I: AsyncRead + AsyncWrite, | ||||
|                 debug_assert!(self.state.cached_headers.is_none()); | ||||
|                 debug_assert!(head.headers.is_empty()); | ||||
|                 self.state.cached_headers = Some(head.headers); | ||||
|                 if !encoder.is_eof() { | ||||
|                     Writing::Body(encoder) | ||||
|                 } else if encoder.is_last() { | ||||
|                     Writing::Closed | ||||
|                 } else { | ||||
|                     Writing::KeepAlive | ||||
|                 } | ||||
|                 Some(encoder) | ||||
|             }, | ||||
|             Err(err) => { | ||||
|                 self.state.error = Some(err); | ||||
|                 Writing::Closed | ||||
|             } | ||||
|         }; | ||||
|                 self.state.writing = Writing::Closed; | ||||
|                 None | ||||
|             }, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // If we know the remote speaks an older version, we try to fix up any messages | ||||
| @@ -474,8 +497,7 @@ where I: AsyncRead + AsyncWrite, | ||||
|  | ||||
|         let state = match self.state.writing { | ||||
|             Writing::Body(ref encoder) => { | ||||
|                 let (encoded, can_keep_alive) = encoder.encode_and_end(chunk); | ||||
|                 self.io.buffer(encoded); | ||||
|                 let can_keep_alive = encoder.encode_and_end(chunk, self.io.write_buf()); | ||||
|                 if can_keep_alive { | ||||
|                     Writing::KeepAlive | ||||
|                 } else { | ||||
|   | ||||
| @@ -4,6 +4,7 @@ use http::{Request, Response, StatusCode}; | ||||
| use tokio_io::{AsyncRead, AsyncWrite}; | ||||
|  | ||||
| use body::{Body, Payload}; | ||||
| use body::internal::FullDataArg; | ||||
| use proto::{BodyLength, Conn, MessageHead, RequestHead, RequestLine, ResponseHead}; | ||||
| use super::Http1Transaction; | ||||
| use service::Service; | ||||
| @@ -20,7 +21,7 @@ pub(crate) trait Dispatch { | ||||
|     type PollItem; | ||||
|     type PollBody; | ||||
|     type RecvItem; | ||||
|     fn poll_msg(&mut self) -> Poll<Option<(Self::PollItem, Option<Self::PollBody>)>, ::Error>; | ||||
|     fn poll_msg(&mut self) -> Poll<Option<(Self::PollItem, Self::PollBody)>, ::Error>; | ||||
|     fn recv_msg(&mut self, msg: ::Result<(Self::RecvItem, Body)>) -> ::Result<()>; | ||||
|     fn poll_ready(&mut self) -> Poll<(), ()>; | ||||
|     fn should_poll(&self) -> bool; | ||||
| @@ -222,14 +223,26 @@ where | ||||
|             if self.is_closing { | ||||
|                 return Ok(Async::Ready(())); | ||||
|             } else if self.body_rx.is_none() && self.conn.can_write_head() && self.dispatch.should_poll() { | ||||
|                 if let Some((head, body)) = try_ready!(self.dispatch.poll_msg()) { | ||||
|                     let body_type = body.as_ref().map(|body| { | ||||
|                         body.content_length() | ||||
|                 if let Some((head, mut body)) = try_ready!(self.dispatch.poll_msg()) { | ||||
|                     // Check if the body knows its full data immediately. | ||||
|                     // | ||||
|                     // If so, we can skip a bit of bookkeeping that streaming | ||||
|                     // bodies need to do. | ||||
|                     if let Some(full) = body.__hyper_full_data(FullDataArg(())).0 { | ||||
|                         self.conn.write_full_msg(head, full); | ||||
|                         return Ok(Async::Ready(())); | ||||
|                     } | ||||
|                     let body_type = if body.is_end_stream() { | ||||
|                         self.body_rx = None; | ||||
|                         None | ||||
|                     } else { | ||||
|                         let btype = body.content_length() | ||||
|                             .map(BodyLength::Known) | ||||
|                             .unwrap_or(BodyLength::Unknown) | ||||
|                     }); | ||||
|                             .or_else(|| Some(BodyLength::Unknown)); | ||||
|                         self.body_rx = Some(body); | ||||
|                         btype | ||||
|                     }; | ||||
|                     self.conn.write_head(head, body_type); | ||||
|                     self.body_rx = body; | ||||
|                 } else { | ||||
|                     self.close(); | ||||
|                     return Ok(Async::Ready(())); | ||||
| @@ -349,7 +362,7 @@ where | ||||
|     type PollBody = Bs; | ||||
|     type RecvItem = RequestHead; | ||||
|  | ||||
|     fn poll_msg(&mut self) -> Poll<Option<(Self::PollItem, Option<Self::PollBody>)>, ::Error> { | ||||
|     fn poll_msg(&mut self) -> Poll<Option<(Self::PollItem, Self::PollBody)>, ::Error> { | ||||
|         if let Some(mut fut) = self.in_flight.take() { | ||||
|             let resp = match fut.poll().map_err(::Error::new_user_service)? { | ||||
|                 Async::Ready(res) => res, | ||||
| @@ -364,11 +377,6 @@ where | ||||
|                 subject: parts.status, | ||||
|                 headers: parts.headers, | ||||
|             }; | ||||
|             let body = if body.is_end_stream() { | ||||
|                 None | ||||
|             } else { | ||||
|                 Some(body) | ||||
|             }; | ||||
|             Ok(Async::Ready(Some((head, body)))) | ||||
|         } else { | ||||
|             unreachable!("poll_msg shouldn't be called if no inflight"); | ||||
| @@ -419,7 +427,7 @@ where | ||||
|     type PollBody = B; | ||||
|     type RecvItem = ResponseHead; | ||||
|  | ||||
|     fn poll_msg(&mut self) -> Poll<Option<(Self::PollItem, Option<Self::PollBody>)>, ::Error> { | ||||
|     fn poll_msg(&mut self) -> Poll<Option<(Self::PollItem, Self::PollBody)>, ::Error> { | ||||
|         match self.rx.poll() { | ||||
|             Ok(Async::Ready(Some((req, mut cb)))) => { | ||||
|                 // check that future hasn't been canceled already | ||||
| @@ -435,12 +443,6 @@ where | ||||
|                             subject: RequestLine(parts.method, parts.uri), | ||||
|                             headers: parts.headers, | ||||
|                         }; | ||||
|  | ||||
|                         let body = if body.is_end_stream() { | ||||
|                             None | ||||
|                         } else { | ||||
|                             Some(body) | ||||
|                         }; | ||||
|                         self.callback = Some(cb); | ||||
|                         Ok(Async::Ready(Some((head, body)))) | ||||
|                     } | ||||
|   | ||||
| @@ -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}; | ||||
|   | ||||
| @@ -99,7 +99,11 @@ where | ||||
|         &mut buf.bytes | ||||
|     } | ||||
|  | ||||
|     pub fn buffer(&mut self, buf: B) { | ||||
|     pub(super) fn write_buf(&mut self) -> &mut WriteBuf<B> { | ||||
|         &mut self.write_buf | ||||
|     } | ||||
|  | ||||
|     pub fn buffer<BB: Buf + Into<B>>(&mut self, buf: BB) { | ||||
|         self.write_buf.buffer(buf) | ||||
|     } | ||||
|  | ||||
| @@ -300,7 +304,7 @@ impl<T: AsRef<[u8]>> Buf for Cursor<T> { | ||||
| } | ||||
|  | ||||
| // an internal buffer to collect writes before flushes | ||||
| struct WriteBuf<B> { | ||||
| pub(super) struct WriteBuf<B> { | ||||
|     /// Re-usable buffer that holds message headers | ||||
|     headers: Cursor<Vec<u8>>, | ||||
|     max_buf_size: usize, | ||||
| @@ -334,7 +338,7 @@ where | ||||
|         WriteBufAuto::new(self) | ||||
|     } | ||||
|  | ||||
|     fn buffer(&mut self, buf: B) { | ||||
|     pub(super) fn buffer<BB: Buf + Into<B>>(&mut self, buf: BB) { | ||||
|         debug_assert!(buf.has_remaining()); | ||||
|         match self.strategy { | ||||
|             Strategy::Flatten => { | ||||
| @@ -342,7 +346,7 @@ where | ||||
|                 head.bytes.put(buf); | ||||
|             }, | ||||
|             Strategy::Auto | Strategy::Queue => { | ||||
|                 self.queue.bufs.push_back(buf); | ||||
|                 self.queue.bufs.push_back(buf.into()); | ||||
|             }, | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -431,6 +431,11 @@ where | ||||
|             }; | ||||
|         } | ||||
|  | ||||
|         if !Server::can_have_body(msg.req_method, msg.head.subject) { | ||||
|             trace!("body not allowed for {:?} {:?}", msg.req_method, msg.head.subject); | ||||
|             encoder = Encoder::length(0); | ||||
|         } | ||||
|  | ||||
|         // cached date is much faster than formatting every request | ||||
|         if !wrote_date { | ||||
|             dst.reserve(date::DATE_VALUE_LENGTH + 8); | ||||
| @@ -479,41 +484,9 @@ where | ||||
| } | ||||
|  | ||||
| impl Server<()> { | ||||
|     /* | ||||
|     fn set_length(head: &mut MessageHead<StatusCode>, body: Option<BodyLength>, method: Option<&Method>) -> Encoder { | ||||
|         // these are here thanks to borrowck | ||||
|         // `if method == Some(&Method::Get)` says the RHS doesn't live long enough | ||||
|         const HEAD: Option<&'static Method> = Some(&Method::HEAD); | ||||
|         const CONNECT: Option<&'static Method> = Some(&Method::CONNECT); | ||||
|  | ||||
|         let can_have_body = { | ||||
|             if method == HEAD { | ||||
|                 false | ||||
|             } else if method == CONNECT && head.subject.is_success() { | ||||
|                 false | ||||
|             } else { | ||||
|                 match head.subject { | ||||
|                     // TODO: support for 1xx codes needs improvement everywhere | ||||
|                     // would be 100...199 => false | ||||
|                     StatusCode::SWITCHING_PROTOCOLS | | ||||
|                     StatusCode::NO_CONTENT | | ||||
|                     StatusCode::NOT_MODIFIED => false, | ||||
|                     _ => true, | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         if let (Some(body), true) = (body, can_have_body) { | ||||
|             set_length(&mut head.headers, body, head.version == Version::HTTP_11) | ||||
|         } else { | ||||
|             head.headers.remove(header::TRANSFER_ENCODING); | ||||
|             if can_have_body { | ||||
|                 headers::content_length_zero(&mut head.headers); | ||||
|             } | ||||
|             Encoder::length(0) | ||||
|         } | ||||
|     fn can_have_body(method: &Option<Method>, status: StatusCode) -> bool { | ||||
|         Server::can_chunked(method, status) | ||||
|     } | ||||
|     */ | ||||
|  | ||||
|     fn can_chunked(method: &Option<Method>, status: StatusCode) -> bool { | ||||
|         if method == &Some(Method::HEAD) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user