Permalink
Browse files
Add ZipTrusted, a faster Zip iterator for trusted-length iterators
This iterator matches the performance of the best-case loop for iterating two slices side by side, with unsafe indexing. For the triple iterator case, it's faster than the standard .zip() iterator but it does not quite match the unsafe code. Benchmarks: zip_loop: Unsafe indexing of 2 slices zip_slices_default_zip: 2 slices with .zip() ziptrusted: 2 slices with ZipTrusted test zip_loop ... bench: 589 ns/iter (+/- 29) test zip_slices_default_zip ... bench: 874 ns/iter (+/- 43) test ziptrusted ... bench: 590 ns/iter (+/- 63) zip_loop3: Unsafe indexing of 3 slices zip_slices_default_zip3: 3 slices with .zip().zip() ziptrusted3: 3 slices with ZipTrusted test zip_loop3 ... bench: 860 ns/iter (+/- 46) test zip_slices_default_zip3 ... bench: 1158 ns/iter (+/- 17) test ziptrusted3 ... bench: 1055 ns/iter (+/- 66)
- Loading branch information...
Showing
with
328 additions
and 3 deletions.
- +176 −0 benches/bench1.rs
- +1 −1 src/lib.rs
- +151 −2 src/ziptuple.rs
| @@ -1,3 +1,5 @@ | |||
| use std::slice; | |||
| use std::vec; | |||
| use std::cmp; | |||
|
|
|||
| #[derive(Clone)] | |||
| @@ -37,7 +39,7 @@ impl<T> Zip<T> where Zip<T>: Iterator | |||
| } | |||
| } | |||
|
|
|||
| macro_rules! impl_zip_iter( | |||
| macro_rules! impl_zip_iter { | |||
| ($($B:ident),*) => ( | |||
| #[allow(non_snake_case)] | |||
| impl<$($B),*> Iterator for Zip<($($B,)*)> | |||
| @@ -82,7 +84,7 @@ macro_rules! impl_zip_iter( | |||
| } | |||
| } | |||
| ); | |||
| ); | |||
| } | |||
|
|
|||
| impl_zip_iter!(A); | |||
| impl_zip_iter!(A, B); | |||
| @@ -93,3 +95,150 @@ impl_zip_iter!(A, B, C, D, E, F); | |||
| impl_zip_iter!(A, B, C, D, E, F, G); | |||
| impl_zip_iter!(A, B, C, D, E, F, G, H); | |||
| impl_zip_iter!(A, B, C, D, E, F, G, H, I); | |||
|
|
|||
|
|
|||
| /// A **TrustedIterator** has exact size, always. | |||
| pub unsafe trait TrustedIterator : ExactSizeIterator | |||
| { | |||
| /* no methods */ | |||
| } | |||
|
|
|||
| unsafe impl TrustedIterator for ::std::ops::Range<usize> { } | |||
| unsafe impl TrustedIterator for ::std::ops::Range<u32> { } | |||
| unsafe impl TrustedIterator for ::std::ops::Range<i32> { } | |||
| unsafe impl TrustedIterator for ::std::ops::Range<u16> { } | |||
| unsafe impl TrustedIterator for ::std::ops::Range<i16> { } | |||
| unsafe impl TrustedIterator for ::std::ops::Range<u8> { } | |||
| unsafe impl TrustedIterator for ::std::ops::Range<i8> { } | |||
| unsafe impl<'a, T> TrustedIterator for slice::Iter<'a, T> { } | |||
| unsafe impl<'a, T> TrustedIterator for slice::IterMut<'a, T> { } | |||
| unsafe impl<T> TrustedIterator for vec::IntoIter<T> { } | |||
|
|
|||
|
|
|||
| #[derive(Clone)] | |||
| /// Create an iterator running multiple iterators in lockstep. | |||
| /// | |||
| /// **ZipTrusted** is an experimental version of **Zip**, and it can only use iterators that are | |||
| /// known to provide their exact size up front. The lockstep iteration can then compile to faster | |||
| /// code, ideally not checking more than once per lap for the end of iteration. | |||
| /// | |||
| /// The iterator **ZipTrusted\<(I, J, ..., M)\>** is formed from a tuple of iterators and yields elements | |||
| /// until any of the subiterators yields **None**. | |||
| /// | |||
| /// Iterator element type is like **(A, B, ..., E)** where **A** to **E** are the respective | |||
| /// subiterator types. | |||
| /// | |||
| /// ## Example | |||
| /// | |||
| /// ``` | |||
| /// use itertools::ZipTrusted; | |||
| /// | |||
| /// // Iterate over three sequences side-by-side | |||
| /// let mut xs = [0, 0, 0]; | |||
| /// let ys = [69, 107, 101]; | |||
| /// | |||
| /// for (i, a, b) in ZipTrusted::new((0i32..100, xs.iter_mut(), ys.iter())) { | |||
| /// *a = i ^ *b; | |||
| /// } | |||
| /// | |||
| /// assert_eq!(xs, [69, 106, 103]); | |||
| /// ``` | |||
| pub struct ZipTrusted<T> { | |||
| length: usize, | |||
| t: T | |||
| } | |||
|
|
|||
| trait SetLength { | |||
| fn set_length(&mut self); | |||
| } | |||
|
|
|||
| impl<T> ZipTrusted<T> where ZipTrusted<T>: SetLength | |||
| { | |||
| /// Create a new **ZipTrusted** from a tuple of iterators. | |||
| #[inline] | |||
| pub fn new(t: T) -> ZipTrusted<T> | |||
| { | |||
| let mut iter = ZipTrusted { | |||
| length: 0, | |||
| t: t, | |||
| }; | |||
| iter.set_length(); | |||
| iter | |||
| } | |||
| } | |||
|
|
|||
| macro_rules! impl_zip_trusted { | |||
| ($($B:ident),*) => ( | |||
| #[allow(non_snake_case)] | |||
| impl<$($B),*> SetLength for ZipTrusted<($($B,)*)> | |||
| where | |||
| $( | |||
| $B: TrustedIterator, | |||
| )* | |||
| { | |||
| #[inline] | |||
| fn set_length(&mut self) | |||
| { | |||
| let len = ::std::usize::MAX; | |||
| let ($(ref $B,)*) = self.t; | |||
| $( | |||
| let (l, h) = $B.size_hint(); | |||
| let len = cmp::min(len, l); | |||
| debug_assert!(Some(l) == h); | |||
| )* | |||
| self.length = len; | |||
| } | |||
| } | |||
|
|
|||
| #[allow(non_snake_case)] | |||
| impl<$($B),*> Iterator for ZipTrusted<($($B,)*)> | |||
| where | |||
| $( | |||
| $B: TrustedIterator, | |||
| )* | |||
| { | |||
| type Item = ($(<$B as Iterator>::Item,)*); | |||
|
|
|||
| fn next(&mut self) -> Option<<Self as Iterator>::Item> | |||
| { | |||
| let ($(ref mut $B,)*) = self.t; | |||
|
|
|||
| if self.length == 0 { | |||
| return None | |||
| } | |||
| $( | |||
| let next_opt = $B.next(); | |||
| let $B; | |||
| unsafe { | |||
| ::std::intrinsics::assume(match next_opt { | |||
| None => false, | |||
| Some(_) => true, | |||
| }); | |||
| $B = match next_opt { | |||
| None => return None, | |||
This comment has been minimized.
This comment has been minimized.
bluss
Owner
|
|||
| Some(elt) => elt | |||
| }; | |||
| } | |||
| )* | |||
| self.length -= 1; | |||
| Some(($($B,)*)) | |||
| } | |||
|
|
|||
| fn size_hint(&self) -> (usize, Option<usize>) | |||
| { | |||
| (self.length, Some(self.length)) | |||
| } | |||
| } | |||
| ); | |||
| } | |||
|
|
|||
| impl_zip_trusted!(A); | |||
| impl_zip_trusted!(A, B); | |||
| impl_zip_trusted!(A, B, C); | |||
| impl_zip_trusted!(A, B, C, D); | |||
| impl_zip_trusted!(A, B, C, D, E); | |||
| impl_zip_trusted!(A, B, C, D, E, F); | |||
| impl_zip_trusted!(A, B, C, D, E, F, G); | |||
| impl_zip_trusted!(A, B, C, D, E, F, G, H); | |||
| impl_zip_trusted!(A, B, C, D, E, F, G, H, I); | |||
|
|
|||
There's a typo here, ys → zs, but fixing that doesn't even change the runtime of the loop, so the benchmarks are the same.