Skip to content

Commit c2150b1

Browse files
bors[bot]cuviper
andcommitted
578: Implement opt_len for ranges of large integers r=nikomatsakis a=cuviper If the length of a range of `i64`, `u64`, `i128`, or `u128` would actually fit in `usize`, we can return that from `opt_len` to enable some optimizations, namely indexed `collect` into vectors. To drive such ranges, we'll actually iterate over `(0..len)` instead, and then map it to the larger type. Co-authored-by: Josh Stone <[email protected]>
2 parents 8ba64ab + e2336fe commit c2150b1

File tree

2 files changed

+88
-8
lines changed

2 files changed

+88
-8
lines changed

src/iter/test.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,21 @@ pub fn execute_unindexed_range() {
5050
assert_eq!(b, c);
5151
}
5252

53+
#[cfg(has_i128)]
54+
#[test]
55+
pub fn execute_pseudo_indexed_range() {
56+
use std::i128::MAX;
57+
let range = MAX - 1024..MAX;
58+
59+
// Given `Some` length, collecting `Vec` will try to act indexed.
60+
let a = range.clone().into_par_iter();
61+
assert_eq!(a.opt_len(), Some(1024));
62+
63+
let b: Vec<i128> = a.map(|i| i + 1).collect();
64+
let c: Vec<i128> = range.map(|i| i + 1).collect();
65+
assert_eq!(b, c);
66+
}
67+
5368
#[test]
5469
pub fn check_map_indexed() {
5570
let a = [1, 2, 3];

src/range.rs

Lines changed: 73 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use iter::*;
2020
use iter::plumbing::*;
2121
use std::ops::Range;
22+
use std::usize;
2223

2324
/// Parallel iterator over a range, implemented for all integer types.
2425
///
@@ -127,11 +128,15 @@ macro_rules! indexed_range_impl {
127128
}
128129
}
129130

131+
trait UnindexedRangeLen<L> {
132+
fn len(&self) -> L;
133+
}
134+
130135
macro_rules! unindexed_range_impl {
131136
( $t:ty, $len_t:ty ) => {
132-
impl IterProducer<$t> {
137+
impl UnindexedRangeLen<$len_t> for Range<$t> {
133138
fn len(&self) -> $len_t {
134-
let Range { start, end } = self.range;
139+
let &Range { start, end } = self;
135140
if end > start {
136141
end.wrapping_sub(start) as $len_t
137142
} else {
@@ -146,15 +151,31 @@ macro_rules! unindexed_range_impl {
146151
fn drive_unindexed<C>(self, consumer: C) -> C::Result
147152
where C: UnindexedConsumer<Self::Item>
148153
{
149-
bridge_unindexed(IterProducer { range: self.range }, consumer)
154+
if let Some(len) = self.opt_len() {
155+
// Drive this in indexed mode for better `collect`.
156+
(0..len).into_par_iter()
157+
.map(|i| self.range.start.wrapping_add(i as $t))
158+
.drive(consumer)
159+
} else {
160+
bridge_unindexed(IterProducer { range: self.range }, consumer)
161+
}
162+
}
163+
164+
fn opt_len(&self) -> Option<usize> {
165+
let len = self.range.len();
166+
if len <= usize::MAX as $len_t {
167+
Some(len as usize)
168+
} else {
169+
None
170+
}
150171
}
151172
}
152173

153174
impl UnindexedProducer for IterProducer<$t> {
154175
type Item = $t;
155176

156177
fn split(mut self) -> (Self, Option<Self>) {
157-
let index = self.len() / 2;
178+
let index = self.range.len() / 2;
158179
if index > 0 {
159180
let mid = self.range.start.wrapping_add(index as $t);
160181
let right = mid .. self.range.end;
@@ -191,7 +212,7 @@ unindexed_range_impl!{i64, u64}
191212
#[cfg(has_i128)] unindexed_range_impl!{i128, u128}
192213

193214
#[test]
194-
pub fn check_range_split_at_overflow() {
215+
fn check_range_split_at_overflow() {
195216
// Note, this split index overflows i8!
196217
let producer = IterProducer { range: -100i8..100 };
197218
let (left, right) = producer.split_at(150);
@@ -202,10 +223,54 @@ pub fn check_range_split_at_overflow() {
202223

203224
#[cfg(has_i128)]
204225
#[test]
205-
pub fn test_i128_len_doesnt_overflow() {
226+
fn test_i128_len_doesnt_overflow() {
227+
use std::{i128, u128};
228+
206229
// Using parse because some versions of rust don't allow long literals
207-
let octillion = "1000000000000000000000000000".parse::<i128>().unwrap();
230+
let octillion: i128 = "1000000000000000000000000000".parse().unwrap();
208231
let producer = IterProducer { range: 0..octillion };
209232

210-
assert_eq!(octillion as u128, producer.len());
233+
assert_eq!(octillion as u128, producer.range.len());
234+
assert_eq!(octillion as u128, (0..octillion).len());
235+
assert_eq!(2 * octillion as u128, (-octillion..octillion).len());
236+
237+
assert_eq!(u128::MAX, (i128::MIN..i128::MAX).len());
238+
239+
}
240+
241+
#[test]
242+
fn test_u64_opt_len() {
243+
use std::{u64, usize};
244+
assert_eq!(Some(100), (0..100u64).into_par_iter().opt_len());
245+
assert_eq!(Some(usize::MAX), (0..usize::MAX as u64).into_par_iter().opt_len());
246+
if (usize::MAX as u64) < u64::MAX {
247+
assert_eq!(None, (0..1 + usize::MAX as u64).into_par_iter().opt_len());
248+
assert_eq!(None, (0..u64::MAX).into_par_iter().opt_len());
249+
}
250+
}
251+
252+
#[cfg(has_i128)]
253+
#[test]
254+
fn test_u128_opt_len() {
255+
use std::{u128, usize};
256+
assert_eq!(Some(100), (0..100u128).into_par_iter().opt_len());
257+
assert_eq!(Some(usize::MAX), (0..usize::MAX as u128).into_par_iter().opt_len());
258+
assert_eq!(None, (0..1 + usize::MAX as u128).into_par_iter().opt_len());
259+
assert_eq!(None, (0..u128::MAX).into_par_iter().opt_len());
260+
}
261+
262+
// `usize as i64` can overflow, so make sure to wrap it appropriately
263+
// when using the `opt_len` "indexed" mode.
264+
#[test]
265+
#[cfg(target_pointer_width = "64")]
266+
fn test_usize_i64_overflow() {
267+
use std::i64;
268+
use ThreadPoolBuilder;
269+
270+
let iter = (-2..i64::MAX).into_par_iter();
271+
assert_eq!(iter.opt_len(), Some(i64::MAX as usize + 2));
272+
273+
// always run with multiple threads to split into, or this will take forever...
274+
let pool = ThreadPoolBuilder::new().num_threads(8).build().unwrap();
275+
pool.install(|| assert_eq!(iter.find_last(|_| true), Some(i64::MAX - 1)));
211276
}

0 commit comments

Comments
 (0)