perf(h1): optimize header encoding
This commit is contained in:
		| @@ -253,7 +253,6 @@ where I: AsyncRead + AsyncWrite, | |||||||
|     pub fn wants_read_again(&mut self) -> bool { |     pub fn wants_read_again(&mut self) -> bool { | ||||||
|         let ret = self.state.notify_read; |         let ret = self.state.notify_read; | ||||||
|         self.state.notify_read = false; |         self.state.notify_read = false; | ||||||
|         trace!("wants_read_again? {}", ret); |  | ||||||
|         ret |         ret | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -146,12 +146,12 @@ impl Encoder { | |||||||
|  |  | ||||||
|                 trace!("sized write, len = {}", len); |                 trace!("sized write, len = {}", len); | ||||||
|                 match (len as u64).cmp(&remaining) { |                 match (len as u64).cmp(&remaining) { | ||||||
|                     Ordering::Greater => { |  | ||||||
|                         (BufKind::Limited(msg.take(remaining as usize)), !self.is_last) |  | ||||||
|                     }, |  | ||||||
|                     Ordering::Equal => { |                     Ordering::Equal => { | ||||||
|                         (BufKind::Exact(msg), !self.is_last) |                         (BufKind::Exact(msg), !self.is_last) | ||||||
|                     }, |                     }, | ||||||
|  |                     Ordering::Greater => { | ||||||
|  |                         (BufKind::Limited(msg.take(remaining as usize)), !self.is_last) | ||||||
|  |                     }, | ||||||
|                     Ordering::Less => { |                     Ordering::Less => { | ||||||
|                         (BufKind::Exact(msg), false) |                         (BufKind::Exact(msg), false) | ||||||
|                     } |                     } | ||||||
|   | |||||||
| @@ -65,20 +65,15 @@ where | |||||||
|             } |             } | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         let mut headers = HeaderMap::with_capacity(headers_len); |  | ||||||
|         let slice = buf.split_to(len).freeze(); |         let slice = buf.split_to(len).freeze(); | ||||||
|         let path = slice.slice(path.0, path.1); |         let path = slice.slice(path.0, path.1); | ||||||
|         // path was found to be utf8 by httparse |  | ||||||
|         let path = Uri::from_shared(path)?; |         let path = Uri::from_shared(path)?; | ||||||
|         let subject = RequestLine( |         let subject = RequestLine( | ||||||
|             method, |             method, | ||||||
|             path, |             path, | ||||||
|         ); |         ); | ||||||
|  |  | ||||||
|         headers.extend(HeadersAsBytesIter { |         let headers = fill_headers(slice, &headers_indices[..headers_len]); | ||||||
|             headers: headers_indices[..headers_len].iter(), |  | ||||||
|             slice: slice, |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         Ok(Some((MessageHead { |         Ok(Some((MessageHead { | ||||||
|             version: version, |             version: version, | ||||||
| @@ -178,7 +173,7 @@ where | |||||||
|         // like req/s goes up about 10% |         // like req/s goes up about 10% | ||||||
|         if !head.headers.contains_key(DATE) { |         if !head.headers.contains_key(DATE) { | ||||||
|             dst.reserve(date::DATE_VALUE_LENGTH + 8); |             dst.reserve(date::DATE_VALUE_LENGTH + 8); | ||||||
|             extend(dst, b"Date: "); |             extend(dst, b"date: "); | ||||||
|             date::extend(dst); |             date::extend(dst); | ||||||
|             extend(dst, b"\r\n"); |             extend(dst, b"\r\n"); | ||||||
|         } |         } | ||||||
| @@ -288,12 +283,10 @@ where | |||||||
|             } |             } | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         let mut headers = HeaderMap::with_capacity(headers_len); |  | ||||||
|         let slice = buf.split_to(len).freeze(); |         let slice = buf.split_to(len).freeze(); | ||||||
|         headers.extend(HeadersAsBytesIter { |  | ||||||
|             headers: headers_indices[..headers_len].iter(), |         let headers = fill_headers(slice, &headers_indices[..headers_len]); | ||||||
|             slice: slice, |  | ||||||
|         }); |  | ||||||
|         Ok(Some((MessageHead { |         Ok(Some((MessageHead { | ||||||
|             version: version, |             version: version, | ||||||
|             subject: status, |             subject: status, | ||||||
| @@ -535,18 +528,21 @@ fn set_content_length(headers: &mut HeaderMap, len: u64) -> Encoder { | |||||||
|     // At this point, there should not be a valid Content-Length |     // At this point, there should not be a valid Content-Length | ||||||
|     // header. However, since we'll be indexing in anyways, we can |     // header. However, since we'll be indexing in anyways, we can | ||||||
|     // warn the user if there was an existing illegal header. |     // warn the user if there was an existing illegal header. | ||||||
|  |     // | ||||||
|  |     // Or at least, we can in theory. It's actually a little bit slower, | ||||||
|  |     // so perhaps only do that while the user is developing/testing. | ||||||
|  |  | ||||||
|  |     if cfg!(debug_assertions) { | ||||||
|         match headers.entry(CONTENT_LENGTH) |         match headers.entry(CONTENT_LENGTH) | ||||||
|             .expect("CONTENT_LENGTH is valid HeaderName") { |             .expect("CONTENT_LENGTH is valid HeaderName") { | ||||||
|             Entry::Occupied(mut cl) => { |             Entry::Occupied(mut cl) => { | ||||||
|             // Uh oh, the user set `Content-Length` headers, but set bad ones. |  | ||||||
|             // This would be an illegal message anyways, so let's try to repair |  | ||||||
|             // with our known good length. |  | ||||||
|             warn!("user provided content-length header was invalid"); |  | ||||||
|  |  | ||||||
|                 // Internal sanity check, we should have already determined |                 // Internal sanity check, we should have already determined | ||||||
|                 // that the header was illegal before calling this function. |                 // that the header was illegal before calling this function. | ||||||
|                 debug_assert!(headers::content_length_parse_all(cl.iter()).is_none()); |                 debug_assert!(headers::content_length_parse_all(cl.iter()).is_none()); | ||||||
|  |                 // Uh oh, the user set `Content-Length` headers, but set bad ones. | ||||||
|  |                 // This would be an illegal message anyways, so let's try to repair | ||||||
|  |                 // with our known good length. | ||||||
|  |                 error!("user provided content-length header was invalid"); | ||||||
|  |  | ||||||
|                 cl.insert(headers::content_length_value(len)); |                 cl.insert(headers::content_length_value(len)); | ||||||
|                 Encoder::length(len) |                 Encoder::length(len) | ||||||
| @@ -556,6 +552,10 @@ fn set_content_length(headers: &mut HeaderMap, len: u64) -> Encoder { | |||||||
|                 Encoder::length(len) |                 Encoder::length(len) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |     } else { | ||||||
|  |         headers.insert(CONTENT_LENGTH, headers::content_length_value(len)); | ||||||
|  |         Encoder::length(len) | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| pub trait OnUpgrade { | pub trait OnUpgrade { | ||||||
| @@ -613,32 +613,19 @@ fn record_header_indices(bytes: &[u8], headers: &[httparse::Header], indices: &m | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| struct HeadersAsBytesIter<'a> { | fn fill_headers(slice: Bytes, indices: &[HeaderIndices]) -> HeaderMap { | ||||||
|     headers: ::std::slice::Iter<'a, HeaderIndices>, |     let mut headers = HeaderMap::with_capacity(indices.len()); | ||||||
|     slice: Bytes, |     for header in indices { | ||||||
| } |         let name = HeaderName::from_bytes(&slice[header.name.0..header.name.1]) | ||||||
|  |  | ||||||
| impl<'a> Iterator for HeadersAsBytesIter<'a> { |  | ||||||
|     type Item = (HeaderName, HeaderValue); |  | ||||||
|     fn next(&mut self) -> Option<Self::Item> { |  | ||||||
|         self.headers.next().map(|header| { |  | ||||||
|             let name = unsafe { |  | ||||||
|                 let bytes = ::std::slice::from_raw_parts( |  | ||||||
|                     self.slice.as_ref().as_ptr().offset(header.name.0 as isize), |  | ||||||
|                     header.name.1 - header.name.0 |  | ||||||
|                 ); |  | ||||||
|                 ::std::str::from_utf8_unchecked(bytes) |  | ||||||
|             }; |  | ||||||
|             let name = HeaderName::from_bytes(name.as_bytes()) |  | ||||||
|             .expect("header name already validated"); |             .expect("header name already validated"); | ||||||
|         let value = unsafe { |         let value = unsafe { | ||||||
|             HeaderValue::from_shared_unchecked( |             HeaderValue::from_shared_unchecked( | ||||||
|                     self.slice.slice(header.value.0, header.value.1) |                 slice.slice(header.value.0, header.value.1) | ||||||
|             ) |             ) | ||||||
|         }; |         }; | ||||||
|             (name, value) |         headers.append(name, value); | ||||||
|         }) |  | ||||||
|     } |     } | ||||||
|  |     headers | ||||||
| } | } | ||||||
|  |  | ||||||
| // Write header names as title case. The header name is assumed to be ASCII, | // Write header names as title case. The header name is assumed to be ASCII, | ||||||
| @@ -960,7 +947,30 @@ mod tests { | |||||||
|  |  | ||||||
|     #[cfg(feature = "nightly")] |     #[cfg(feature = "nightly")] | ||||||
|     #[bench] |     #[bench] | ||||||
|     fn bench_server_transaction_encode(b: &mut Bencher) { |     fn bench_parse_short(b: &mut Bencher) { | ||||||
|  |         let mut raw = BytesMut::from( | ||||||
|  |             b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n".to_vec() | ||||||
|  |         ); | ||||||
|  |         let len = raw.len(); | ||||||
|  |  | ||||||
|  |         b.bytes = len as u64; | ||||||
|  |         b.iter(|| { | ||||||
|  |             Server::parse(&mut raw).unwrap(); | ||||||
|  |             restart(&mut raw, len); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         fn restart(b: &mut BytesMut, len: usize) { | ||||||
|  |             b.reserve(1); | ||||||
|  |             unsafe { | ||||||
|  |                 b.set_len(len); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[cfg(feature = "nightly")] | ||||||
|  |     #[bench] | ||||||
|  |     fn bench_server_encode_headers_preset(b: &mut Bencher) { | ||||||
|         use http::header::HeaderValue; |         use http::header::HeaderValue; | ||||||
|         use proto::BodyLength; |         use proto::BodyLength; | ||||||
|  |  | ||||||
| @@ -978,4 +988,22 @@ mod tests { | |||||||
|             ::test::black_box(vec); |             ::test::black_box(vec); | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     #[cfg(feature = "nightly")] | ||||||
|  |     #[bench] | ||||||
|  |     fn bench_server_encode_no_headers(b: &mut Bencher) { | ||||||
|  |         use proto::BodyLength; | ||||||
|  |  | ||||||
|  |         let len = 76; | ||||||
|  |         b.bytes = len as u64; | ||||||
|  |  | ||||||
|  |         let head = MessageHead::default(); | ||||||
|  |  | ||||||
|  |         b.iter(|| { | ||||||
|  |             let mut vec = Vec::new(); | ||||||
|  |             Server::encode(head.clone(), Some(BodyLength::Known(10)), &mut None, false, &mut vec).unwrap(); | ||||||
|  |             //assert_eq!(vec.len(), len); | ||||||
|  |             ::test::black_box(vec); | ||||||
|  |         }) | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user