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%
```
This commit is contained in:
bluss
2017-10-08 17:41:32 +02:00
parent af8b746fc4
commit 793ad30be9
+34 -2
View File
@@ -805,6 +805,21 @@ impl<'a, A: Array> Drop for Drain<'a, A>
} }
} }
struct ScopeExitGuard<T, Data, F>
where F: FnMut(&Data, &mut T)
{
value: T,
data: Data,
f: F,
}
impl<T, Data, F> Drop for ScopeExitGuard<T, Data, F>
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<A: Array> Extend<A::Item> for ArrayVec<A> { impl<A: Array> Extend<A::Item> for ArrayVec<A> {
fn extend<T: IntoIterator<Item=A::Item>>(&mut self, iter: T) { fn extend<T: IntoIterator<Item=A::Item>>(&mut self, iter: T) {
let take = self.capacity() - self.len(); let take = self.capacity() - self.len();
for elt in iter.into_iter().take(take) {
unsafe { unsafe {
self.push_unchecked(elt); 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;
} }
} }
} }