From af8b746fc40cbe65b5741f469bd655e63b648890 Mon Sep 17 00:00:00 2001 From: bluss Date: Sun, 8 Oct 2017 17:27:24 +0200 Subject: [PATCH 1/2] FEAT: Add benchmarks for .extend() --- Cargo.toml | 5 +++++ benches/extend.rs | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 benches/extend.rs diff --git a/Cargo.toml b/Cargo.toml index 6597a97a..5f3100f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,11 @@ version = "1.0" [dev-dependencies] matches = { version = "0.1" } +bencher = "0.1.4" + +[[bench]] +name = "extend" +harness = false [features] default = ["std"] diff --git a/benches/extend.rs b/benches/extend.rs new file mode 100644 index 00000000..d380a7ed --- /dev/null +++ b/benches/extend.rs @@ -0,0 +1,43 @@ + +extern crate arrayvec; +#[macro_use] extern crate bencher; + +use arrayvec::ArrayVec; + +use bencher::Bencher; + +fn extend_with_constant(b: &mut Bencher) { + let mut v = ArrayVec::<[u8; 512]>::new(); + let cap = v.capacity(); + b.iter(|| { + v.clear(); + v.extend((0..cap).map(|_| 1)); + v[0] + }); + b.bytes = v.capacity() as u64; +} + +fn extend_with_range(b: &mut Bencher) { + let mut v = ArrayVec::<[u8; 512]>::new(); + let cap = v.capacity(); + b.iter(|| { + v.clear(); + v.extend((0..cap).map(|x| x as _)); + v[0] + }); + b.bytes = v.capacity() as u64; +} + +fn extend_with_slice(b: &mut Bencher) { + let mut v = ArrayVec::<[u8; 512]>::new(); + let data = [1; 512]; + b.iter(|| { + v.clear(); + v.extend(data.iter().cloned()); + v[0] + }); + b.bytes = v.capacity() as u64; +} + +benchmark_group!(benches, extend_with_constant, extend_with_range, extend_with_slice); +benchmark_main!(benches); From 793ad30be9ff71a134848308e819f3fb706f1fe2 Mon Sep 17 00:00:00 2001 From: bluss Date: Sun, 8 Oct 2017 17:41:32 +0200 Subject: [PATCH 2/2] FEAT: Improve .extend() performance We have to use the "SetLenOnDrop" pattern (see stdlib Vec) here. Keep the length in a separate variable, write it back on scope exit. To help the compiler with alias analysis and stuff. We update the length to handle panic in the iteration of the user's iterator, without dropping any elements on the floor. Note: This code was tested without the scope guard using the new option -Zmutable-noalias, which had no effect here. benchmark: ``` name before.txt ns/iter after.txt ns/iter diff ns/iter diff % extend_with_constant 280 (1828 MB/s) 74 (6918 MB/s) -206 -73.57% extend_with_range 1,285 (398 MB/s) 979 (522 MB/s) -306 -23.81% extend_with_slice 29 (17655 MB/s) 14 (36571 MB/s) -15 -51.72% ``` --- src/lib.rs | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 11343b8f..9e25a713 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -805,6 +805,21 @@ impl<'a, A: Array> Drop for Drain<'a, A> } } +struct ScopeExitGuard + where F: FnMut(&Data, &mut T) +{ + value: T, + data: Data, + f: F, +} + +impl Drop for ScopeExitGuard + where F: FnMut(&Data, &mut T) +{ + fn drop(&mut self) { + (self.f)(&self.data, &mut self.value) + } +} @@ -815,9 +830,26 @@ impl<'a, A: Array> Drop for Drain<'a, A> impl Extend for ArrayVec { fn extend>(&mut self, iter: T) { let take = self.capacity() - self.len(); - for elt in iter.into_iter().take(take) { - unsafe { - self.push_unchecked(elt); + unsafe { + let len = self.len(); + let mut ptr = self.as_mut_ptr().offset(len as isize); + // Keep the length in a separate variable, write it back on scope + // exit. To help the compiler with alias analysis and stuff. + // We update the length to handle panic in the iteration of the + // user's iterator, without dropping any elements on the floor. + let mut guard = ScopeExitGuard { + value: self, + data: len, + f: |&len, self_| { + unsafe { + self_.set_len(len) + } + } + }; + for elt in iter.into_iter().take(take) { + ptr::write(ptr, elt); + ptr = ptr.offset(1); + guard.data += 1; } } }