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, |     flushed: bool, | ||||||
|     inner: T, |     inner: T, | ||||||
|     max_read_vecs: usize, |     max_read_vecs: usize, | ||||||
|  |     num_writes: usize, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl<T> AsyncIo<T> { | impl<T> AsyncIo<T> { | ||||||
| @@ -81,6 +82,7 @@ impl<T> AsyncIo<T> { | |||||||
|             flushed: false, |             flushed: false, | ||||||
|             inner: inner, |             inner: inner, | ||||||
|             max_read_vecs: READ_VECS_CNT, |             max_read_vecs: READ_VECS_CNT, | ||||||
|  |             num_writes: 0, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -107,6 +109,10 @@ impl<T> AsyncIo<T> { | |||||||
|     pub fn blocked(&self) -> bool { |     pub fn blocked(&self) -> bool { | ||||||
|         self.blocked |         self.blocked | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     pub fn num_writes(&self) -> usize { | ||||||
|  |         self.num_writes | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl AsyncIo<Buf> { | impl AsyncIo<Buf> { | ||||||
| @@ -160,6 +166,7 @@ impl<T: Read> Read for AsyncIo<T> { | |||||||
|  |  | ||||||
| impl<T: Write> Write for AsyncIo<T> { | impl<T: Write> Write for AsyncIo<T> { | ||||||
|     fn write(&mut self, data: &[u8]) -> io::Result<usize> { |     fn write(&mut self, data: &[u8]) -> io::Result<usize> { | ||||||
|  |         self.num_writes += 1; | ||||||
|         if let Some(err) = self.error.take() { |         if let Some(err) = self.error.take() { | ||||||
|             Err(err) |             Err(err) | ||||||
|         } else if self.bytes_until_block == 0 { |         } 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 i = ::bytes::Buf::bytes_vec(&buf, &mut bufs[..self.max_read_vecs]); | ||||||
|             let mut n = 0; |             let mut n = 0; | ||||||
|             let mut ret = Ok(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] { |             for iovec in &bufs[..i] { | ||||||
|                 match self.write(iovec) { |                 match self.write(iovec) { | ||||||
|                     Ok(num) => { |                     Ok(num) => { | ||||||
| @@ -216,6 +226,7 @@ impl<T: Read + Write> AsyncWrite for AsyncIo<T> { | |||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |             self.num_writes = num_writes + 1; | ||||||
|             ret |             ret | ||||||
|         }; |         }; | ||||||
|         match r { |         match r { | ||||||
|   | |||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | use std::cell::Cell; | ||||||
| use std::collections::VecDeque; | use std::collections::VecDeque; | ||||||
| use std::fmt; | use std::fmt; | ||||||
| use std::io; | use std::io; | ||||||
| @@ -55,7 +56,7 @@ where | |||||||
|         self.write_buf.set_strategy(if enabled { |         self.write_buf.set_strategy(if enabled { | ||||||
|             Strategy::Flatten |             Strategy::Flatten | ||||||
|         } else { |         } else { | ||||||
|             Strategy::Queue |             Strategy::Auto | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -68,6 +69,11 @@ where | |||||||
|         self.read_buf.as_ref() |         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> { |     pub fn write_buf_mut(&mut self) -> &mut Vec<u8> { | ||||||
|         let buf = self.write_buf.head_mut(); |         let buf = self.write_buf.head_mut(); | ||||||
|         buf.maybe_reset(); |         buf.maybe_reset(); | ||||||
| @@ -154,7 +160,7 @@ where | |||||||
|             try_nb!(self.io.flush()); |             try_nb!(self.io.flush()); | ||||||
|         } else { |         } else { | ||||||
|             loop { |             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); |                 debug!("flushed {} bytes", n); | ||||||
|                 if self.write_buf.remaining() == 0 { |                 if self.write_buf.remaining() == 0 { | ||||||
|                     break; |                     break; | ||||||
| @@ -263,7 +269,7 @@ impl<B> WriteBuf<B> { | |||||||
|         WriteBuf { |         WriteBuf { | ||||||
|             buf: BufDeque::new(), |             buf: BufDeque::new(), | ||||||
|             max_buf_size: DEFAULT_MAX_BUFFER_SIZE, |             max_buf_size: DEFAULT_MAX_BUFFER_SIZE, | ||||||
|             strategy: Strategy::Queue, |             strategy: Strategy::Auto, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -277,6 +283,11 @@ where | |||||||
|         self.strategy = strategy; |         self.strategy = strategy; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     #[inline] | ||||||
|  |     fn auto(&mut self) -> WriteBufAuto<B> { | ||||||
|  |         WriteBufAuto::new(self) | ||||||
|  |     } | ||||||
|  |  | ||||||
|     fn buffer(&mut self, buf: B) { |     fn buffer(&mut self, buf: B) { | ||||||
|         match self.strategy { |         match self.strategy { | ||||||
|             Strategy::Flatten => { |             Strategy::Flatten => { | ||||||
| @@ -284,7 +295,7 @@ where | |||||||
|                 head.maybe_reset(); |                 head.maybe_reset(); | ||||||
|                 head.bytes.put(buf); |                 head.bytes.put(buf); | ||||||
|             }, |             }, | ||||||
|             Strategy::Queue => { |             Strategy::Auto | Strategy::Queue => { | ||||||
|                 self.buf.bufs.push_back(VecOrBuf::Buf(buf)); |                 self.buf.bufs.push_back(VecOrBuf::Buf(buf)); | ||||||
|             }, |             }, | ||||||
|         } |         } | ||||||
| @@ -295,8 +306,7 @@ where | |||||||
|             Strategy::Flatten => { |             Strategy::Flatten => { | ||||||
|                 self.remaining() < self.max_buf_size |                 self.remaining() < self.max_buf_size | ||||||
|             }, |             }, | ||||||
|             Strategy::Queue => { |             Strategy::Auto | Strategy::Queue => { | ||||||
|                 // for now, the simplest of heuristics |  | ||||||
|                 self.buf.bufs.len() < MAX_BUF_LIST_BUFFERS |                 self.buf.bufs.len() < MAX_BUF_LIST_BUFFERS | ||||||
|                     && self.remaining() < self.max_buf_size |                     && 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)] | #[derive(Debug)] | ||||||
| enum Strategy { | enum Strategy { | ||||||
|  |     Auto, | ||||||
|     Flatten, |     Flatten, | ||||||
|     Queue, |     Queue, | ||||||
| } | } | ||||||
| @@ -536,4 +606,117 @@ mod tests { | |||||||
|         buffered.flush().unwrap(); |         buffered.flush().unwrap(); | ||||||
|         assert_eq!(buffered.io, b"hello"); |         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