From b9ccd2a8666f4aa6650db977da7b5e678f9066f9 Mon Sep 17 00:00:00 2001 From: Stepan Koltsov Date: Sun, 2 Jul 2017 00:10:09 +0300 Subject: [PATCH] Optimize Bytes::slice for short slices (#136) Slice operation should return inline when possible It is cheaper than atomic increment/decrement. Before this patch: ``` test slice_avg_le_inline_from_arc ... bench: 28,582 ns/iter (+/- 3,880) test slice_empty ... bench: 8,797 ns/iter (+/- 1,325) test slice_large_le_inline_from_arc ... bench: 27,684 ns/iter (+/- 5,920) test slice_short_from_arc ... bench: 27,439 ns/iter (+/- 5,783) ``` After this patch: ``` test slice_avg_le_inline_from_arc ... bench: 18,872 ns/iter (+/- 2,937) test slice_empty ... bench: 9,136 ns/iter (+/- 1,908) test slice_large_le_inline_from_arc ... bench: 18,052 ns/iter (+/- 2,981) test slice_short_from_arc ... bench: 18,200 ns/iter (+/- 2,534) ``` --- benches/bytes.rs | 49 ++++++++++++++++++++++++++++++++++++++++-------- src/bytes.rs | 12 ++++++++---- 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/benches/bytes.rs b/benches/bytes.rs index 86eb9c4..07494f5 100644 --- a/benches/bytes.rs +++ b/benches/bytes.rs @@ -154,19 +154,52 @@ fn from_long_slice(b: &mut Bencher) { #[bench] fn slice_empty(b: &mut Bencher) { b.iter(|| { - // Use empty vec to avoid measure of allocation/deallocation - let bytes = Bytes::from(Vec::new()); - (bytes.slice(0, 0), bytes) + let b = Bytes::from(vec![17; 1024]).clone(); + for i in 0..1000 { + test::black_box(b.slice(i % 100, i % 100)); + } }) } #[bench] -fn slice_not_empty(b: &mut Bencher) { +fn slice_short_from_arc(b: &mut Bencher) { b.iter(|| { - let b = Bytes::from(b"aabbccddeeffgghh".to_vec()); - for _ in 0..1024 { - test::black_box(b.slice(3, 5)); - test::black_box(&b); + // `clone` is to convert to ARC + let b = Bytes::from(vec![17; 1024]).clone(); + for i in 0..1000 { + test::black_box(b.slice(1, 2 + i % 10)); + } + }) +} + +// Keep in sync with bytes.rs +#[cfg(target_pointer_width = "64")] +const INLINE_CAP: usize = 4 * 8 - 1; +#[cfg(target_pointer_width = "32")] +const INLINE_CAP: usize = 4 * 4 - 1; + +#[bench] +fn slice_avg_le_inline_from_arc(b: &mut Bencher) { + b.iter(|| { + // `clone` is to convert to ARC + let b = Bytes::from(vec![17; 1024]).clone(); + for i in 0..1000 { + // [1, INLINE_CAP] + let len = 1 + i % (INLINE_CAP - 1); + test::black_box(b.slice(i % 10, i % 10 + len)); + } + }) +} + +#[bench] +fn slice_large_le_inline_from_arc(b: &mut Bencher) { + b.iter(|| { + // `clone` is to convert to ARC + let b = Bytes::from(vec![17; 1024]).clone(); + for i in 0..1000 { + // [INLINE_CAP - 10, INLINE_CAP] + let len = INLINE_CAP - 9 + i % 10; + test::black_box(b.slice(i % 10, i % 10 + len)); } }) } diff --git a/src/bytes.rs b/src/bytes.rs index 5bd4fde..88a8c8f 100644 --- a/src/bytes.rs +++ b/src/bytes.rs @@ -492,9 +492,11 @@ impl Bytes { /// Requires that `begin <= end` and `end <= self.len()`, otherwise slicing /// will panic. pub fn slice(&self, begin: usize, end: usize) -> Bytes { - if begin == end { - assert!(begin <= self.len()); - return Bytes::new(); + assert!(begin <= end); + assert!(end <= self.len()); + + if end - begin <= INLINE_CAP { + return Bytes::from(&self[begin..end]); } let mut ret = self.clone(); @@ -1430,7 +1432,9 @@ impl<'a> From<&'a [u8]> for BytesMut { fn from(src: &'a [u8]) -> BytesMut { let len = src.len(); - if len <= INLINE_CAP { + if len == 0 { + BytesMut::new() + } else if len <= INLINE_CAP { unsafe { let mut inner: Inner = mem::uninitialized();