Skip to content

Commit 7328588

Browse files
committed
Auto merge of rust-lang#85528 - the8472:iter-markers, r=dtolnay
Implement iterator specialization traits on more adapters This adds * `TrustedLen` to `Skip` and `StepBy` * `TrustedRandomAccess` to `Skip` * `InPlaceIterable` and `SourceIter` to `Copied` and `Cloned` The first two might improve performance in the compiler itself since `skip` is used in several places. Constellations that would exercise the last point are probably rare since it would require an owning iterator that has references as Items somewhere in its iterator pipeline. Improvements for `Skip`: ``` # old test iter::bench_skip_trusted_random_access ... bench: 8,335 ns/iter (+/- 90) # new test iter::bench_skip_trusted_random_access ... bench: 2,753 ns/iter (+/- 27) ```
2 parents 867d39c + 37d26c7 commit 7328588

File tree

8 files changed

+139
-16
lines changed

8 files changed

+139
-16
lines changed

library/alloc/tests/vec.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -1166,10 +1166,14 @@ fn test_from_iter_partially_drained_in_place_specialization() {
11661166
#[test]
11671167
fn test_from_iter_specialization_with_iterator_adapters() {
11681168
fn assert_in_place_trait<T: InPlaceIterable>(_: &T) {}
1169-
let src: Vec<usize> = vec![0usize; 256];
1169+
let owned: Vec<usize> = vec![0usize; 256];
1170+
let refd: Vec<&usize> = owned.iter().collect();
1171+
let src: Vec<&&usize> = refd.iter().collect();
11701172
let srcptr = src.as_ptr();
11711173
let iter = src
11721174
.into_iter()
1175+
.copied()
1176+
.cloned()
11731177
.enumerate()
11741178
.map(|i| i.0 + i.1)
11751179
.zip(std::iter::repeat(1usize))
@@ -1180,7 +1184,7 @@ fn test_from_iter_specialization_with_iterator_adapters() {
11801184
assert_in_place_trait(&iter);
11811185
let sink = iter.collect::<Result<Vec<_>, _>>().unwrap();
11821186
let sinkptr = sink.as_ptr();
1183-
assert_eq!(srcptr, sinkptr as *const usize);
1187+
assert_eq!(srcptr as *const usize, sinkptr as *const usize);
11841188
}
11851189

11861190
#[test]

library/core/benches/iter.rs

+13
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,19 @@ fn bench_skip_then_zip(b: &mut Bencher) {
391391
});
392392
}
393393

394+
#[bench]
395+
fn bench_skip_trusted_random_access(b: &mut Bencher) {
396+
let v: Vec<u64> = black_box(vec![42; 10000]);
397+
let mut sink = [0; 10000];
398+
399+
b.iter(|| {
400+
for (val, idx) in v.iter().skip(8).zip(0..10000) {
401+
sink[idx] += val;
402+
}
403+
sink
404+
});
405+
}
406+
394407
#[bench]
395408
fn bench_filter_count(b: &mut Bencher) {
396409
b.iter(|| (0i64..1000000).map(black_box).filter(|x| x % 3 == 0).count())

library/core/src/iter/adapters/cloned.rs

+23-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use crate::iter::adapters::{
2-
zip::try_get_unchecked, TrustedRandomAccess, TrustedRandomAccessNoCoerce,
2+
zip::try_get_unchecked, SourceIter, TrustedRandomAccess, TrustedRandomAccessNoCoerce,
33
};
4-
use crate::iter::{FusedIterator, TrustedLen, UncheckedIterator};
4+
use crate::iter::{FusedIterator, InPlaceIterable, TrustedLen, UncheckedIterator};
55
use crate::ops::Try;
6+
use core::num::NonZeroUsize;
67

78
/// An iterator that clones the elements of an underlying iterator.
89
///
@@ -167,3 +168,23 @@ impl<I: Default> Default for Cloned<I> {
167168
Self::new(Default::default())
168169
}
169170
}
171+
172+
#[unstable(issue = "none", feature = "inplace_iteration")]
173+
unsafe impl<I> SourceIter for Cloned<I>
174+
where
175+
I: SourceIter,
176+
{
177+
type Source = I::Source;
178+
179+
#[inline]
180+
unsafe fn as_inner(&mut self) -> &mut I::Source {
181+
// SAFETY: unsafe function forwarding to unsafe function with the same requirements
182+
unsafe { SourceIter::as_inner(&mut self.it) }
183+
}
184+
}
185+
186+
#[unstable(issue = "none", feature = "inplace_iteration")]
187+
unsafe impl<I: InPlaceIterable> InPlaceIterable for Cloned<I> {
188+
const EXPAND_BY: Option<NonZeroUsize> = I::EXPAND_BY;
189+
const MERGE_BY: Option<NonZeroUsize> = I::MERGE_BY;
190+
}

library/core/src/iter/adapters/copied.rs

+22-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::iter::adapters::{
2-
zip::try_get_unchecked, TrustedRandomAccess, TrustedRandomAccessNoCoerce,
2+
zip::try_get_unchecked, SourceIter, TrustedRandomAccess, TrustedRandomAccessNoCoerce,
33
};
4-
use crate::iter::{FusedIterator, TrustedLen};
4+
use crate::iter::{FusedIterator, InPlaceIterable, TrustedLen};
55
use crate::mem::MaybeUninit;
66
use crate::mem::SizedTypeProperties;
77
use crate::num::NonZeroUsize;
@@ -255,3 +255,23 @@ impl<I: Default> Default for Copied<I> {
255255
Self::new(Default::default())
256256
}
257257
}
258+
259+
#[unstable(issue = "none", feature = "inplace_iteration")]
260+
unsafe impl<I> SourceIter for Copied<I>
261+
where
262+
I: SourceIter,
263+
{
264+
type Source = I::Source;
265+
266+
#[inline]
267+
unsafe fn as_inner(&mut self) -> &mut I::Source {
268+
// SAFETY: unsafe function forwarding to unsafe function with the same requirements
269+
unsafe { SourceIter::as_inner(&mut self.it) }
270+
}
271+
}
272+
273+
#[unstable(issue = "none", feature = "inplace_iteration")]
274+
unsafe impl<I: InPlaceIterable> InPlaceIterable for Copied<I> {
275+
const EXPAND_BY: Option<NonZeroUsize> = I::EXPAND_BY;
276+
const MERGE_BY: Option<NonZeroUsize> = I::MERGE_BY;
277+
}

library/core/src/iter/adapters/skip.rs

+51-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
use crate::intrinsics::unlikely;
2+
use crate::iter::adapters::zip::try_get_unchecked;
23
use crate::iter::TrustedFused;
3-
use crate::iter::{adapters::SourceIter, FusedIterator, InPlaceIterable};
4+
use crate::iter::{
5+
adapters::SourceIter, FusedIterator, InPlaceIterable, TrustedLen, TrustedRandomAccess,
6+
TrustedRandomAccessNoCoerce,
7+
};
48
use crate::num::NonZeroUsize;
59
use crate::ops::{ControlFlow, Try};
610

@@ -152,6 +156,32 @@ where
152156

153157
NonZeroUsize::new(n).map_or(Ok(()), Err)
154158
}
159+
160+
#[doc(hidden)]
161+
unsafe fn __iterator_get_unchecked(&mut self, idx: usize) -> Self::Item
162+
where
163+
Self: TrustedRandomAccessNoCoerce,
164+
{
165+
// SAFETY: the caller must uphold the contract for
166+
// `Iterator::__iterator_get_unchecked`.
167+
//
168+
// Dropping the skipped prefix when index 0 is passed is safe
169+
// since
170+
// * the caller passing index 0 means that the inner iterator has more items than `self.n`
171+
// * TRA contract requires that get_unchecked will only be called once
172+
// (unless elements are copyable)
173+
// * it does not conflict with in-place iteration since index 0 must be accessed
174+
// before something is written into the storage used by the prefix
175+
unsafe {
176+
if Self::MAY_HAVE_SIDE_EFFECT && idx == 0 {
177+
for skipped_idx in 0..self.n {
178+
drop(try_get_unchecked(&mut self.iter, skipped_idx));
179+
}
180+
}
181+
182+
try_get_unchecked(&mut self.iter, idx + self.n)
183+
}
184+
}
155185
}
156186

157187
#[stable(feature = "rust1", since = "1.0.0")]
@@ -237,3 +267,23 @@ unsafe impl<I: InPlaceIterable> InPlaceIterable for Skip<I> {
237267
const EXPAND_BY: Option<NonZeroUsize> = I::EXPAND_BY;
238268
const MERGE_BY: Option<NonZeroUsize> = I::MERGE_BY;
239269
}
270+
271+
#[doc(hidden)]
272+
#[unstable(feature = "trusted_random_access", issue = "none")]
273+
unsafe impl<I> TrustedRandomAccess for Skip<I> where I: TrustedRandomAccess {}
274+
275+
#[doc(hidden)]
276+
#[unstable(feature = "trusted_random_access", issue = "none")]
277+
unsafe impl<I> TrustedRandomAccessNoCoerce for Skip<I>
278+
where
279+
I: TrustedRandomAccessNoCoerce,
280+
{
281+
const MAY_HAVE_SIDE_EFFECT: bool = I::MAY_HAVE_SIDE_EFFECT;
282+
}
283+
284+
// SAFETY: This adapter is shortening. TrustedLen requires the upper bound to be calculated correctly.
285+
// These requirements can only be satisfied when the upper bound of the inner iterator's upper
286+
// bound is never `None`. I: TrustedRandomAccess happens to provide this guarantee while
287+
// I: TrustedLen would not.
288+
#[unstable(feature = "trusted_len", issue = "37572")]
289+
unsafe impl<I> TrustedLen for Skip<I> where I: Iterator + TrustedRandomAccess {}

library/core/src/iter/adapters/step_by.rs

+9-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::convert::TryFrom;
22
use crate::{
33
intrinsics,
4-
iter::{from_fn, TrustedLen},
4+
iter::{from_fn, TrustedLen, TrustedRandomAccess},
55
ops::{Range, Try},
66
};
77

@@ -124,6 +124,14 @@ where
124124
#[stable(feature = "iterator_step_by", since = "1.28.0")]
125125
impl<I> ExactSizeIterator for StepBy<I> where I: ExactSizeIterator {}
126126

127+
// SAFETY: This adapter is shortening. TrustedLen requires the upper bound to be calculated correctly.
128+
// These requirements can only be satisfied when the upper bound of the inner iterator's upper
129+
// bound is never `None`. I: TrustedRandomAccess happens to provide this guarantee while
130+
// I: TrustedLen would not.
131+
// This also covers the Range specializations since the ranges also implement TRA
132+
#[unstable(feature = "trusted_len", issue = "37572")]
133+
unsafe impl<I> TrustedLen for StepBy<I> where I: Iterator + TrustedRandomAccess {}
134+
127135
trait SpecRangeSetup<T> {
128136
fn setup(inner: T, step: usize) -> T;
129137
}
@@ -480,12 +488,6 @@ macro_rules! spec_int_ranges {
480488
acc
481489
}
482490
}
483-
484-
/// Safety: This macro is only applied to ranges over types <= usize
485-
/// which means the inner length is guaranteed to fit into a usize and so
486-
/// the outer length calculation won't encounter clamped values
487-
#[unstable(feature = "trusted_len", issue = "37572")]
488-
unsafe impl TrustedLen for StepBy<Range<$t>> {}
489491
)*)
490492
}
491493

library/core/tests/iter/adapters/step_by.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,8 @@ fn test_iterator_step_by_size_hint() {
220220
assert_eq!(it.len(), 3);
221221

222222
// Cannot be TrustedLen as a step greater than one makes an iterator
223-
// with (usize::MAX, None) no longer meet the safety requirements
223+
// with (usize::MAX, None) no longer meet the safety requirements.
224+
// Exception: The inner iterator is known to have a len() <= usize::MAX
224225
trait TrustedLenCheck {
225226
fn test(self) -> bool;
226227
}
@@ -235,7 +236,9 @@ fn test_iterator_step_by_size_hint() {
235236
}
236237
}
237238
assert!(TrustedLenCheck::test(a.iter()));
238-
assert!(!TrustedLenCheck::test(a.iter().step_by(1)));
239+
assert!(TrustedLenCheck::test(a.iter().step_by(1)));
240+
assert!(TrustedLenCheck::test(a.iter().chain(a.iter())));
241+
assert!(!TrustedLenCheck::test(a.iter().chain(a.iter()).step_by(1)));
239242
}
240243

241244
#[test]

library/core/tests/iter/range.rs

+10
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,16 @@ fn test_range_inclusive_size_hint() {
474474
assert_eq!((imin..=imax + 1).size_hint(), (usize::MAX, None));
475475
}
476476

477+
#[test]
478+
fn test_range_trusted_random_access() {
479+
let mut range = 0..10;
480+
unsafe {
481+
assert_eq!(range.next(), Some(0));
482+
assert_eq!(range.__iterator_get_unchecked(0), 1);
483+
assert_eq!(range.__iterator_get_unchecked(1), 2);
484+
}
485+
}
486+
477487
#[test]
478488
fn test_double_ended_range() {
479489
assert_eq!((11..14).rev().collect::<Vec<_>>(), [13, 12, 11]);

0 commit comments

Comments
 (0)