From e44472209856bf9518e5ba4491f658d7f9a5abf2 Mon Sep 17 00:00:00 2001 From: kohensu <kohensu@gmail.com> Date: Fri, 27 Apr 2018 19:07:27 +0200 Subject: [PATCH] Improve performance of Buf::get_*() (#195) The new implementation tries to get the data directly from bytes() (this is possible most of the time) and if there is not enough data in bytes() use the previous code: copy the needed bytes in a temporary buffer before returning the data Here the bench results: Before After x-faster get_f32::cursor 64 ns/iter (+/- 0) 20 ns/iter (+/- 0) 3.2 get_f32::tbuf_1 77 ns/iter (+/- 1) 34 ns/iter (+/- 0) 2.3 get_f32::tbuf_1_costly 87 ns/iter (+/- 0) 62 ns/iter (+/- 0) 1.4 get_f32::tbuf_2 151 ns/iter (+/- 18) 160 ns/iter (+/- 1) 0.9 get_f32::tbuf_2_costly 180 ns/iter (+/- 2) 187 ns/iter (+/- 2) 1.0 get_f64::cursor 67 ns/iter (+/- 0) 21 ns/iter (+/- 0) 3.2 get_f64::tbuf_1 80 ns/iter (+/- 0) 35 ns/iter (+/- 0) 2.3 get_f64::tbuf_1_costly 82 ns/iter (+/- 3) 60 ns/iter (+/- 0) 1.4 get_f64::tbuf_2 154 ns/iter (+/- 1) 164 ns/iter (+/- 0) 0.9 get_f64::tbuf_2_costly 170 ns/iter (+/- 2) 187 ns/iter (+/- 1) 0.9 get_u16::cursor 66 ns/iter (+/- 0) 20 ns/iter (+/- 0) 3.3 get_u16::tbuf_1 77 ns/iter (+/- 0) 35 ns/iter (+/- 0) 2.2 get_u16::tbuf_1_costly 85 ns/iter (+/- 2) 62 ns/iter (+/- 0) 1.4 get_u16::tbuf_2 147 ns/iter (+/- 0) 154 ns/iter (+/- 0) 1.0 get_u16::tbuf_2_costly 160 ns/iter (+/- 1) 177 ns/iter (+/- 0) 0.9 get_u32::cursor 64 ns/iter (+/- 0) 20 ns/iter (+/- 0) 3.2 get_u32::tbuf_1 77 ns/iter (+/- 0) 35 ns/iter (+/- 0) 2.2 get_u32::tbuf_1_costly 91 ns/iter (+/- 2) 63 ns/iter (+/- 0) 1.4 get_u32::tbuf_2 151 ns/iter (+/- 40) 157 ns/iter (+/- 0) 1.0 get_u32::tbuf_2_costly 162 ns/iter (+/- 0) 180 ns/iter (+/- 0) 0.9 get_u64::cursor 67 ns/iter (+/- 0) 20 ns/iter (+/- 0) 3.4 get_u64::tbuf_1 78 ns/iter (+/- 0) 35 ns/iter (+/- 1) 2.2 get_u64::tbuf_1_costly 87 ns/iter (+/- 1) 59 ns/iter (+/- 1) 1.5 get_u64::tbuf_2 154 ns/iter (+/- 0) 160 ns/iter (+/- 0) 1.0 get_u64::tbuf_2_costly 168 ns/iter (+/- 0) 184 ns/iter (+/- 0) 0.9 get_u8::cursor 64 ns/iter (+/- 0) 19 ns/iter (+/- 0) 3.4 get_u8::tbuf_1 77 ns/iter (+/- 0) 35 ns/iter (+/- 0) 2.2 get_u8::tbuf_1_costly 68 ns/iter (+/- 0) 51 ns/iter (+/- 0) 1.3 get_u8::tbuf_2 85 ns/iter (+/- 0) 43 ns/iter (+/- 0) 2.0 get_u8::tbuf_2_costly 75 ns/iter (+/- 0) 61 ns/iter (+/- 0) 1.2 get_u8::option 77 ns/iter (+/- 0) 59 ns/iter (+/- 0) 1.3 Improvement on the basic std::Cursor implementation are clearly visible. Other implementations are specific to the bench tests and just map a static slice. Different variant are: - tbuf_1: only one call of 'bytes()' is needed. - tbuf_2: two calls of 'bytes()' is needed to read more than one byte. - _costly version are implemented with #[inline(never)] on 'bytes()', 'remaining()' and 'advance()'. The cases that are slower (slightly) correspond to implementations that are not really realistic: more than one byte is never possible in one time --- src/buf/buf.rs | 126 +++++++++++++++++++++++-------------------------- 1 file changed, 60 insertions(+), 66 deletions(-) diff --git a/src/buf/buf.rs b/src/buf/buf.rs index 4462364..991ac67 100644 --- a/src/buf/buf.rs +++ b/src/buf/buf.rs @@ -4,6 +4,38 @@ use iovec::IoVec; use std::{cmp, io, ptr}; +macro_rules! buf_get_impl { + ($this:ident, $size:expr, $conv:path) => ({ + // try to convert directly from the bytes + let ret = { + // this Option<ret> trick is to avoid keeping a borrow on self + // when advance() is called (mut borrow) and to call bytes() only once + if let Some(src) = $this.bytes().get(..($size)) { + Some($conv(src)) + } else { + None + } + }; + if let Some(ret) = ret { + // if the direct convertion was possible, advance and return + $this.advance($size); + return ret; + } else { + // if not we copy the bytes in a temp buffer then convert + let mut buf = [0; ($size)]; + $this.copy_to_slice(&mut buf); // (do the advance) + return $conv(&buf); + } + }); + ($this:ident, $buf_size:expr, $conv:path, $len_to_read:expr) => ({ + // The same trick as above does not improve the best case speed. + // It seems to be linked to the way the method is optimised by the compiler + let mut buf = [0; ($buf_size)]; + $this.copy_to_slice(&mut buf[..($len_to_read)]); + return $conv(&buf[..($len_to_read)], $len_to_read); + }); +} + /// Read bytes from a buffer. /// /// A buffer stores bytes in memory such that read operations are infallible. @@ -243,9 +275,10 @@ pub trait Buf { /// /// This function panics if there is no more remaining data in `self`. fn get_u8(&mut self) -> u8 { - let mut buf = [0; 1]; - self.copy_to_slice(&mut buf); - buf[0] + assert!(self.remaining() >= 1); + let ret = self.bytes()[0]; + self.advance(1); + ret } /// Gets a signed 8 bit integer from `self`. @@ -266,9 +299,10 @@ pub trait Buf { /// /// This function panics if there is no more remaining data in `self`. fn get_i8(&mut self) -> i8 { - let mut buf = [0; 1]; - self.copy_to_slice(&mut buf); - buf[0] as i8 + assert!(self.remaining() >= 1); + let ret = self.bytes()[0] as i8; + self.advance(1); + ret } /// Gets an unsigned 16 bit integer from `self` in big-endian byte order. @@ -289,9 +323,7 @@ pub trait Buf { /// /// This function panics if there is not enough remaining data in `self`. fn get_u16(&mut self) -> u16 { - let mut buf = [0; 2]; - self.copy_to_slice(&mut buf); - BigEndian::read_u16(&buf) + buf_get_impl!(self, 2, BigEndian::read_u16); } /// Gets an unsigned 16 bit integer from `self` in little-endian byte order. @@ -312,9 +344,7 @@ pub trait Buf { /// /// This function panics if there is not enough remaining data in `self`. fn get_u16_le(&mut self) -> u16 { - let mut buf = [0; 2]; - self.copy_to_slice(&mut buf); - LittleEndian::read_u16(&buf) + buf_get_impl!(self, 2, LittleEndian::read_u16); } /// Gets a signed 16 bit integer from `self` in big-endian byte order. @@ -335,9 +365,7 @@ pub trait Buf { /// /// This function panics if there is not enough remaining data in `self`. fn get_i16(&mut self) -> i16 { - let mut buf = [0; 2]; - self.copy_to_slice(&mut buf); - BigEndian::read_i16(&buf) + buf_get_impl!(self, 2, BigEndian::read_i16); } /// Gets a signed 16 bit integer from `self` in little-endian byte order. @@ -358,9 +386,7 @@ pub trait Buf { /// /// This function panics if there is not enough remaining data in `self`. fn get_i16_le(&mut self) -> i16 { - let mut buf = [0; 2]; - self.copy_to_slice(&mut buf); - LittleEndian::read_i16(&buf) + buf_get_impl!(self, 2, LittleEndian::read_i16); } /// Gets an unsigned 32 bit integer from `self` in the big-endian byte order. @@ -381,9 +407,7 @@ pub trait Buf { /// /// This function panics if there is not enough remaining data in `self`. fn get_u32(&mut self) -> u32 { - let mut buf = [0; 4]; - self.copy_to_slice(&mut buf); - BigEndian::read_u32(&buf) + buf_get_impl!(self, 4, BigEndian::read_u32); } /// Gets an unsigned 32 bit integer from `self` in the little-endian byte order. @@ -404,9 +428,7 @@ pub trait Buf { /// /// This function panics if there is not enough remaining data in `self`. fn get_u32_le(&mut self) -> u32 { - let mut buf = [0; 4]; - self.copy_to_slice(&mut buf); - LittleEndian::read_u32(&buf) + buf_get_impl!(self, 4, LittleEndian::read_u32); } /// Gets a signed 32 bit integer from `self` in big-endian byte order. @@ -427,9 +449,7 @@ pub trait Buf { /// /// This function panics if there is not enough remaining data in `self`. fn get_i32(&mut self) -> i32 { - let mut buf = [0; 4]; - self.copy_to_slice(&mut buf); - BigEndian::read_i32(&buf) + buf_get_impl!(self, 4, BigEndian::read_i32); } /// Gets a signed 32 bit integer from `self` in little-endian byte order. @@ -450,9 +470,7 @@ pub trait Buf { /// /// This function panics if there is not enough remaining data in `self`. fn get_i32_le(&mut self) -> i32 { - let mut buf = [0; 4]; - self.copy_to_slice(&mut buf); - LittleEndian::read_i32(&buf) + buf_get_impl!(self, 4, LittleEndian::read_i32); } /// Gets an unsigned 64 bit integer from `self` in big-endian byte order. @@ -473,9 +491,7 @@ pub trait Buf { /// /// This function panics if there is not enough remaining data in `self`. fn get_u64(&mut self) -> u64 { - let mut buf = [0; 8]; - self.copy_to_slice(&mut buf); - BigEndian::read_u64(&buf) + buf_get_impl!(self, 8, BigEndian::read_u64); } /// Gets an unsigned 64 bit integer from `self` in little-endian byte order. @@ -496,9 +512,7 @@ pub trait Buf { /// /// This function panics if there is not enough remaining data in `self`. fn get_u64_le(&mut self) -> u64 { - let mut buf = [0; 8]; - self.copy_to_slice(&mut buf); - LittleEndian::read_u64(&buf) + buf_get_impl!(self, 8, LittleEndian::read_u64); } /// Gets a signed 64 bit integer from `self` in big-endian byte order. @@ -519,9 +533,7 @@ pub trait Buf { /// /// This function panics if there is not enough remaining data in `self`. fn get_i64(&mut self) -> i64 { - let mut buf = [0; 8]; - self.copy_to_slice(&mut buf); - BigEndian::read_i64(&buf) + buf_get_impl!(self, 8, BigEndian::read_i64); } /// Gets a signed 64 bit integer from `self` in little-endian byte order. @@ -542,9 +554,7 @@ pub trait Buf { /// /// This function panics if there is not enough remaining data in `self`. fn get_i64_le(&mut self) -> i64 { - let mut buf = [0; 8]; - self.copy_to_slice(&mut buf); - LittleEndian::read_i64(&buf) + buf_get_impl!(self, 8, LittleEndian::read_i64); } /// Gets an unsigned n-byte integer from `self` in big-endian byte order. @@ -565,9 +575,7 @@ pub trait Buf { /// /// This function panics if there is not enough remaining data in `self`. fn get_uint(&mut self, nbytes: usize) -> u64 { - let mut buf = [0; 8]; - self.copy_to_slice(&mut buf[..nbytes]); - BigEndian::read_uint(&buf[..nbytes], nbytes) + buf_get_impl!(self, 8, BigEndian::read_uint, nbytes); } /// Gets an unsigned n-byte integer from `self` in little-endian byte order. @@ -588,9 +596,7 @@ pub trait Buf { /// /// This function panics if there is not enough remaining data in `self`. fn get_uint_le(&mut self, nbytes: usize) -> u64 { - let mut buf = [0; 8]; - self.copy_to_slice(&mut buf[..nbytes]); - LittleEndian::read_uint(&buf[..nbytes], nbytes) + buf_get_impl!(self, 8, LittleEndian::read_uint, nbytes); } /// Gets a signed n-byte integer from `self` in big-endian byte order. @@ -611,9 +617,7 @@ pub trait Buf { /// /// This function panics if there is not enough remaining data in `self`. fn get_int(&mut self, nbytes: usize) -> i64 { - let mut buf = [0; 8]; - self.copy_to_slice(&mut buf[..nbytes]); - BigEndian::read_int(&buf[..nbytes], nbytes) + buf_get_impl!(self, 8, BigEndian::read_int, nbytes); } /// Gets a signed n-byte integer from `self` in little-endian byte order. @@ -634,9 +638,7 @@ pub trait Buf { /// /// This function panics if there is not enough remaining data in `self`. fn get_int_le(&mut self, nbytes: usize) -> i64 { - let mut buf = [0; 8]; - self.copy_to_slice(&mut buf[..nbytes]); - LittleEndian::read_int(&buf[..nbytes], nbytes) + buf_get_impl!(self, 8, LittleEndian::read_int, nbytes); } /// Gets an IEEE754 single-precision (4 bytes) floating point number from @@ -658,9 +660,7 @@ pub trait Buf { /// /// This function panics if there is not enough remaining data in `self`. fn get_f32(&mut self) -> f32 { - let mut buf = [0; 4]; - self.copy_to_slice(&mut buf); - BigEndian::read_f32(&buf) + buf_get_impl!(self, 4, BigEndian::read_f32); } /// Gets an IEEE754 single-precision (4 bytes) floating point number from @@ -682,9 +682,7 @@ pub trait Buf { /// /// This function panics if there is not enough remaining data in `self`. fn get_f32_le(&mut self) -> f32 { - let mut buf = [0; 4]; - self.copy_to_slice(&mut buf); - LittleEndian::read_f32(&buf) + buf_get_impl!(self, 4, LittleEndian::read_f32); } /// Gets an IEEE754 double-precision (8 bytes) floating point number from @@ -706,9 +704,7 @@ pub trait Buf { /// /// This function panics if there is not enough remaining data in `self`. fn get_f64(&mut self) -> f64 { - let mut buf = [0; 8]; - self.copy_to_slice(&mut buf); - BigEndian::read_f64(&buf) + buf_get_impl!(self, 8, BigEndian::read_f64); } /// Gets an IEEE754 double-precision (8 bytes) floating point number from @@ -730,9 +726,7 @@ pub trait Buf { /// /// This function panics if there is not enough remaining data in `self`. fn get_f64_le(&mut self) -> f64 { - let mut buf = [0; 8]; - self.copy_to_slice(&mut buf); - LittleEndian::read_f64(&buf) + buf_get_impl!(self, 8, LittleEndian::read_f64); } /// Transforms a `Buf` into a concrete buffer. -- GitLab