perf(h1): convert buffer to flatten strategy with auto detection
This commit is contained in:
		
							
								
								
									
										11
									
								
								src/mock.rs
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								src/mock.rs
									
									
									
									
									
								
							| @@ -70,6 +70,7 @@ pub struct AsyncIo<T> { | ||||
|     flushed: bool, | ||||
|     inner: T, | ||||
|     max_read_vecs: usize, | ||||
|     num_writes: usize, | ||||
| } | ||||
|  | ||||
| impl<T> AsyncIo<T> { | ||||
| @@ -81,6 +82,7 @@ impl<T> AsyncIo<T> { | ||||
|             flushed: false, | ||||
|             inner: inner, | ||||
|             max_read_vecs: READ_VECS_CNT, | ||||
|             num_writes: 0, | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -107,6 +109,10 @@ impl<T> AsyncIo<T> { | ||||
|     pub fn blocked(&self) -> bool { | ||||
|         self.blocked | ||||
|     } | ||||
|  | ||||
|     pub fn num_writes(&self) -> usize { | ||||
|         self.num_writes | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl AsyncIo<Buf> { | ||||
| @@ -160,6 +166,7 @@ impl<T: Read> Read for AsyncIo<T> { | ||||
|  | ||||
| impl<T: Write> Write for AsyncIo<T> { | ||||
|     fn write(&mut self, data: &[u8]) -> io::Result<usize> { | ||||
|         self.num_writes += 1; | ||||
|         if let Some(err) = self.error.take() { | ||||
|             Err(err) | ||||
|         } else if self.bytes_until_block == 0 { | ||||
| @@ -198,6 +205,9 @@ impl<T: Read + Write> AsyncWrite for AsyncIo<T> { | ||||
|             let i = ::bytes::Buf::bytes_vec(&buf, &mut bufs[..self.max_read_vecs]); | ||||
|             let mut n = 0; | ||||
|             let mut ret = Ok(0); | ||||
|             // each call to write() will increase our count, but we assume | ||||
|             // that if iovecs are used, its really only 1 write call. | ||||
|             let num_writes = self.num_writes; | ||||
|             for iovec in &bufs[..i] { | ||||
|                 match self.write(iovec) { | ||||
|                     Ok(num) => { | ||||
| @@ -216,6 +226,7 @@ impl<T: Read + Write> AsyncWrite for AsyncIo<T> { | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             self.num_writes = num_writes + 1; | ||||
|             ret | ||||
|         }; | ||||
|         match r { | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| use std::cell::Cell; | ||||
| use std::collections::VecDeque; | ||||
| use std::fmt; | ||||
| use std::io; | ||||
| @@ -55,7 +56,7 @@ where | ||||
|         self.write_buf.set_strategy(if enabled { | ||||
|             Strategy::Flatten | ||||
|         } else { | ||||
|             Strategy::Queue | ||||
|             Strategy::Auto | ||||
|         }); | ||||
|     } | ||||
|  | ||||
| @@ -68,6 +69,11 @@ where | ||||
|         self.read_buf.as_ref() | ||||
|     } | ||||
|  | ||||
|     //TODO(perf): don't return a `&mut Vec<u8>`, but a wrapper | ||||
|     //that protects the Vec when growing. Specifically, if this | ||||
|     //Vec couldn't be reset, as it's position isn't at the end, | ||||
|     //any new reserves will copy the bytes before the position, | ||||
|     //which is unnecessary. | ||||
|     pub fn write_buf_mut(&mut self) -> &mut Vec<u8> { | ||||
|         let buf = self.write_buf.head_mut(); | ||||
|         buf.maybe_reset(); | ||||
| @@ -154,7 +160,7 @@ where | ||||
|             try_nb!(self.io.flush()); | ||||
|         } else { | ||||
|             loop { | ||||
|                 let n = try_ready!(self.io.write_buf(&mut self.write_buf)); | ||||
|                 let n = try_ready!(self.io.write_buf(&mut self.write_buf.auto())); | ||||
|                 debug!("flushed {} bytes", n); | ||||
|                 if self.write_buf.remaining() == 0 { | ||||
|                     break; | ||||
| @@ -263,7 +269,7 @@ impl<B> WriteBuf<B> { | ||||
|         WriteBuf { | ||||
|             buf: BufDeque::new(), | ||||
|             max_buf_size: DEFAULT_MAX_BUFFER_SIZE, | ||||
|             strategy: Strategy::Queue, | ||||
|             strategy: Strategy::Auto, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -277,6 +283,11 @@ where | ||||
|         self.strategy = strategy; | ||||
|     } | ||||
|  | ||||
|     #[inline] | ||||
|     fn auto(&mut self) -> WriteBufAuto<B> { | ||||
|         WriteBufAuto::new(self) | ||||
|     } | ||||
|  | ||||
|     fn buffer(&mut self, buf: B) { | ||||
|         match self.strategy { | ||||
|             Strategy::Flatten => { | ||||
| @@ -284,7 +295,7 @@ where | ||||
|                 head.maybe_reset(); | ||||
|                 head.bytes.put(buf); | ||||
|             }, | ||||
|             Strategy::Queue => { | ||||
|             Strategy::Auto | Strategy::Queue => { | ||||
|                 self.buf.bufs.push_back(VecOrBuf::Buf(buf)); | ||||
|             }, | ||||
|         } | ||||
| @@ -295,8 +306,7 @@ where | ||||
|             Strategy::Flatten => { | ||||
|                 self.remaining() < self.max_buf_size | ||||
|             }, | ||||
|             Strategy::Queue => { | ||||
|                 // for now, the simplest of heuristics | ||||
|             Strategy::Auto | Strategy::Queue => { | ||||
|                 self.buf.bufs.len() < MAX_BUF_LIST_BUFFERS | ||||
|                     && self.remaining() < self.max_buf_size | ||||
|             }, | ||||
| @@ -355,8 +365,68 @@ impl<B: Buf> Buf for WriteBuf<B> { | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// Detects when wrapped `WriteBuf` is used for vectored IO, and | ||||
| /// adjusts the `WriteBuf` strategy if not. | ||||
| struct WriteBufAuto<'a, B: Buf + 'a> { | ||||
|     bytes_called: Cell<bool>, | ||||
|     bytes_vec_called: Cell<bool>, | ||||
|     inner: &'a mut WriteBuf<B>, | ||||
| } | ||||
|  | ||||
| impl<'a, B: Buf> WriteBufAuto<'a, B> { | ||||
|     fn new(inner: &'a mut WriteBuf<B>) -> WriteBufAuto<'a, B> { | ||||
|         WriteBufAuto { | ||||
|             bytes_called: Cell::new(false), | ||||
|             bytes_vec_called: Cell::new(false), | ||||
|             inner: inner, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a, B: Buf> Buf for WriteBufAuto<'a, B> { | ||||
|     #[inline] | ||||
|     fn remaining(&self) -> usize { | ||||
|         self.inner.remaining() | ||||
|     } | ||||
|  | ||||
|     #[inline] | ||||
|     fn bytes(&self) -> &[u8] { | ||||
|         self.bytes_called.set(true); | ||||
|         self.inner.bytes() | ||||
|     } | ||||
|  | ||||
|     #[inline] | ||||
|     fn advance(&mut self, cnt: usize) { | ||||
|         self.inner.advance(cnt) | ||||
|     } | ||||
|  | ||||
|     #[inline] | ||||
|     fn bytes_vec<'t>(&'t self, dst: &mut [&'t IoVec]) -> usize { | ||||
|         self.bytes_vec_called.set(true); | ||||
|         self.inner.bytes_vec(dst) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a, B: Buf + 'a> Drop for WriteBufAuto<'a, B> { | ||||
|     fn drop(&mut self) { | ||||
|         if let Strategy::Auto = self.inner.strategy { | ||||
|             if self.bytes_vec_called.get() { | ||||
|                 self.inner.strategy = Strategy::Queue; | ||||
|             } else if self.bytes_called.get() { | ||||
|                 trace!("detected no usage of vectored write, flattening"); | ||||
|                 self.inner.strategy = Strategy::Flatten; | ||||
|                 let mut vec = Vec::new(); | ||||
|                 vec.put(&mut self.inner.buf); | ||||
|                 self.inner.buf.bufs.push_back(VecOrBuf::Vec(Cursor::new(vec))); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| #[derive(Debug)] | ||||
| enum Strategy { | ||||
|     Auto, | ||||
|     Flatten, | ||||
|     Queue, | ||||
| } | ||||
| @@ -536,4 +606,117 @@ mod tests { | ||||
|         buffered.flush().unwrap(); | ||||
|         assert_eq!(buffered.io, b"hello"); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn write_buf_queue() { | ||||
|         extern crate pretty_env_logger; | ||||
|         let _ = pretty_env_logger::try_init(); | ||||
|  | ||||
|         let mock = AsyncIo::new_buf(vec![], 1024); | ||||
|         let mut buffered = Buffered::<_, Cursor<Vec<u8>>>::new(mock); | ||||
|  | ||||
|         buffered.write_buf_mut().extend(b"hello "); | ||||
|         buffered.buffer(Cursor::new(b"world, ".to_vec())); | ||||
|         buffered.write_buf_mut().extend(b"it's "); | ||||
|         buffered.buffer(Cursor::new(b"hyper!".to_vec())); | ||||
|         buffered.flush().unwrap(); | ||||
|  | ||||
|         assert_eq!(buffered.io, b"hello world, it's hyper!"); | ||||
|         assert_eq!(buffered.io.num_writes(), 1); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn write_buf_reclaim_vec() { | ||||
|         extern crate pretty_env_logger; | ||||
|         let _ = pretty_env_logger::try_init(); | ||||
|  | ||||
|         let mock = AsyncIo::new_buf(vec![], 1024); | ||||
|         let mut buffered = Buffered::<_, Cursor<Vec<u8>>>::new(mock); | ||||
|  | ||||
|         buffered.write_buf_mut().extend(b"hello "); | ||||
|         assert_eq!(buffered.write_buf.buf.bufs.len(), 1); | ||||
|         buffered.write_buf_mut().extend(b"world, "); | ||||
|         assert_eq!(buffered.write_buf.buf.bufs.len(), 1); | ||||
|  | ||||
|         // after flushing, reclaim the Vec | ||||
|         buffered.flush().unwrap(); | ||||
|         assert_eq!(buffered.write_buf.remaining(), 0); | ||||
|         assert_eq!(buffered.write_buf.buf.bufs.len(), 1); | ||||
|  | ||||
|         // add a user buf in the way | ||||
|         buffered.buffer(Cursor::new(b"it's ".to_vec())); | ||||
|         // and then add more hyper bytes | ||||
|         buffered.write_buf_mut().extend(b"hyper!"); | ||||
|         buffered.flush().unwrap(); | ||||
|         assert_eq!(buffered.write_buf.buf.bufs.len(), 1); | ||||
|  | ||||
|         assert_eq!(buffered.io, b"hello world, it's hyper!"); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn write_buf_flatten() { | ||||
|         extern crate pretty_env_logger; | ||||
|         let _ = pretty_env_logger::try_init(); | ||||
|  | ||||
|         let mock = AsyncIo::new_buf(vec![], 1024); | ||||
|         let mut buffered = Buffered::<_, Cursor<Vec<u8>>>::new(mock); | ||||
|         buffered.write_buf.set_strategy(Strategy::Flatten); | ||||
|  | ||||
|         buffered.write_buf_mut().extend(b"hello "); | ||||
|         buffered.buffer(Cursor::new(b"world, ".to_vec())); | ||||
|         buffered.write_buf_mut().extend(b"it's "); | ||||
|         buffered.buffer(Cursor::new(b"hyper!".to_vec())); | ||||
|         assert_eq!(buffered.write_buf.buf.bufs.len(), 1); | ||||
|  | ||||
|         buffered.flush().unwrap(); | ||||
|  | ||||
|         assert_eq!(buffered.io, b"hello world, it's hyper!"); | ||||
|         assert_eq!(buffered.io.num_writes(), 1); | ||||
|         assert_eq!(buffered.write_buf.buf.bufs.len(), 1); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn write_buf_auto_flatten() { | ||||
|         extern crate pretty_env_logger; | ||||
|         let _ = pretty_env_logger::try_init(); | ||||
|  | ||||
|         let mut mock = AsyncIo::new_buf(vec![], 1024); | ||||
|         mock.max_read_vecs(0); // disable vectored IO | ||||
|         let mut buffered = Buffered::<_, Cursor<Vec<u8>>>::new(mock); | ||||
|  | ||||
|         // we have 4 buffers, but hope to detect that vectored IO isn't | ||||
|         // being used, and switch to flattening automatically, | ||||
|         // resulting in only 2 writes | ||||
|         buffered.write_buf_mut().extend(b"hello "); | ||||
|         buffered.buffer(Cursor::new(b"world, ".to_vec())); | ||||
|         buffered.write_buf_mut().extend(b"it's hyper!"); | ||||
|         //buffered.buffer(Cursor::new(b"hyper!".to_vec())); | ||||
|         buffered.flush().unwrap(); | ||||
|  | ||||
|         assert_eq!(buffered.io, b"hello world, it's hyper!"); | ||||
|         assert_eq!(buffered.io.num_writes(), 2); | ||||
|         assert_eq!(buffered.write_buf.buf.bufs.len(), 1); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn write_buf_queue_does_not_auto() { | ||||
|         extern crate pretty_env_logger; | ||||
|         let _ = pretty_env_logger::try_init(); | ||||
|  | ||||
|         let mut mock = AsyncIo::new_buf(vec![], 1024); | ||||
|         mock.max_read_vecs(0); // disable vectored IO | ||||
|         let mut buffered = Buffered::<_, Cursor<Vec<u8>>>::new(mock); | ||||
|         buffered.write_buf.set_strategy(Strategy::Queue); | ||||
|  | ||||
|         // we have 4 buffers, and vec IO disabled, but explicitly said | ||||
|         // don't try to auto detect (via setting strategy above) | ||||
|         buffered.write_buf_mut().extend(b"hello "); | ||||
|         buffered.buffer(Cursor::new(b"world, ".to_vec())); | ||||
|         buffered.write_buf_mut().extend(b"it's "); | ||||
|         buffered.buffer(Cursor::new(b"hyper!".to_vec())); | ||||
|         buffered.flush().unwrap(); | ||||
|  | ||||
|         assert_eq!(buffered.io, b"hello world, it's hyper!"); | ||||
|         assert_eq!(buffered.io.num_writes(), 4); | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user