Bug 1933199 - update Rust rayon crate to v1.10.0 r=kershaw,supply-chain-reviewers,valentin

[mozilla/neqo#2135](https://github.com/mozilla/neqo/pull/2135) adds `mtu` crate
to `neqo-*`. `mtu` crate depends on `windows-bindgen`. `windows-bindgen` depends
on `rayon` `1.7`.

On the other hand mozilla-central depends on [`rayon`
`v1.6.1`](https://searchfox.org/mozilla-central/rev/7987501f2c2ed1914e5c682bd328ace9c4a7c6cd/Cargo.lock#5149-5157).

Given that mozilla-central allows at most one version of each crate, let's
update mozilla-central to `rayon` `1.10.0`, i.e. the most recent version.

See https://github.com/mozilla/neqo/pull/2135#issuecomment-2497077670 for details.

Differential Revision: https://phabricator.services.mozilla.com/D230127
This commit is contained in:
Max Inden
2024-11-28 14:45:20 +00:00
parent 5123d0ecd3
commit 80d40104c4
67 changed files with 3072 additions and 425 deletions

8
Cargo.lock generated
View File

@@ -5158,9 +5158,9 @@ checksum = "42a9830a0e1b9fb145ebb365b8bc4ccd75f290f98c0247deafbbe2c75cefb544"
[[package]]
name = "rayon"
version = "1.6.1"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7"
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
dependencies = [
"either",
"rayon-core",
@@ -5168,9 +5168,9 @@ dependencies = [
[[package]]
name = "rayon-core"
version = "1.12.0"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",

View File

@@ -1668,15 +1668,35 @@ who = "David Cook <dcook@divviup.org>"
criteria = "safe-to-deploy"
version = "0.6.3"
[[audits.isrg.audits.rayon-core]]
[[audits.isrg.audits.rayon]]
who = "Brandon Pitman <bran@bran.land>"
criteria = "safe-to-deploy"
delta = "1.10.2 -> 1.11.0"
delta = "1.6.1 -> 1.7.0"
[[audits.isrg.audits.rayon-core]]
[[audits.isrg.audits.rayon]]
who = "David Cook <dcook@divviup.org>"
criteria = "safe-to-deploy"
delta = "1.11.0 -> 1.12.0"
delta = "1.7.0 -> 1.8.0"
[[audits.isrg.audits.rayon]]
who = "Ameer Ghani <inahga@divviup.org>"
criteria = "safe-to-deploy"
delta = "1.8.0 -> 1.8.1"
[[audits.isrg.audits.rayon]]
who = "Brandon Pitman <bran@bran.land>"
criteria = "safe-to-deploy"
delta = "1.8.1 -> 1.9.0"
[[audits.isrg.audits.rayon]]
who = "Brandon Pitman <bran@bran.land>"
criteria = "safe-to-deploy"
delta = "1.9.0 -> 1.10.0"
[[audits.isrg.audits.rayon-core]]
who = "Ameer Ghani <inahga@divviup.org>"
criteria = "safe-to-deploy"
version = "1.12.1"
[[audits.isrg.audits.sha2]]
who = "David Cook <dcook@divviup.org>"

View File

@@ -1 +1 @@
{"files":{"Cargo.toml":"c25083c4b0fc46e0f63b88b4bc346a1c034698c16ece8f04ce72dd2af9cc7ffb","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"0621878e61f0d0fda054bcbe02df75192c28bde1ecc8289cbd86aeba2dd72720","README.md":"7281273bea1d5fdc57731513cf9f0e3b911d06ac9905b03a8375a1324951c35b","build.rs":"fa31cb198b772600d100a7c403ddedccef637d2e6b2da431fa7f02ca41307fc6","src/broadcast/mod.rs":"2c9a84e7e6e5e8d8e23e28d6f2703825d7d6af59f0a16bc6125d5f0d25bd7598","src/broadcast/test.rs":"fe50fc868e67d855a9f71e078b0c3a7780e789652abb4b586accb4ccf035e872","src/compile_fail/mod.rs":"4d70256295bd64691a8c1994b323559cda1888e85f0b45ca55711541c257dcb6","src/compile_fail/quicksort_race1.rs":"35f498cda38f4eb6e00117f78ed68e0fe5a3fa61c25303d9c08a19bda345bc6c","src/compile_fail/quicksort_race2.rs":"cbb40030c7867cae34bb373b6ec5d97c2ac6de39bc917f47879b30eb423924bc","src/compile_fail/quicksort_race3.rs":"8403643e64c969306b1a9b1243378e6ccdd313b57e1878dbd31393618082fd35","src/compile_fail/rc_return.rs":"197894803d8df58fc8005d90c86b90cd98f1972f1a4b57438516a565df35903f","src/compile_fail/rc_upvar.rs":"42d110429621f407ef0dada1306dab116583d2c782a99894204dd8e0ccd2312f","src/compile_fail/scope_join_bad.rs":"892959949f77cadfc07458473e7a290301182027ca64428df5a8ce887be0892b","src/job.rs":"06de0c2add2e303b6383bf11f5f0d75775c1efe6aa7bc16de3992117f1012f09","src/join/mod.rs":"7638c0fc1da1a2d2b14673c8a2e0f87d26c24232cebee26fd334bdc2caa80886","src/join/test.rs":"157db5306e8df89a8eea19dbba499f26c2f44d9803cb36a796c852a9a695821e","src/latch.rs":"81da563b29b03455cd22073d243eaed081e953873c14ac202f6605cd3dac09a5","src/lib.rs":"53bb01b167d56c6ace035666b570fff648eedf03a5c8c415ec37136a0ef35697","src/private.rs":"152f6d65ce4741616a1dec796b9442f78a018d38bb040f76c4cd85008333a3bb","src/registry.rs":"c464c4fdb36c85cfe2a10d6196802b036bb76985d737ab9a67d708f908877672","src/scope/mod.rs":"421a5561093928b1d0081d34c2bff78377055d8f6de0689088f52fe476d3a56a","src/scope/test.rs":"d4f068cae4ee4483b41bd3054582d96e74ced46eb57361e7510ef62d4318d340","src/sleep/README.md":"e1ac1a5556cf257f38b7654feb0615c208d9186fefbe52a584d4fe6545d7c373","src/sleep/counters.rs":"e9eccc7d76d17415156c12d30cc7bf89a5c64ca5742965bb4e6c1ce23c2782e9","src/sleep/mod.rs":"23a9116f84653a5f68ab21c910f1dea5314a5332fdc9473a87710974f4b2c717","src/spawn/mod.rs":"745494a18fc4901c37ea2f45a1324abf5bd2a4d9c840620956e6633755116d88","src/spawn/test.rs":"a28f8943f28a4cef642b6429c538b1df879c9eb1db9927ce69b97c686bf81173","src/test.rs":"7d0dee06fcf41bddf77449a85cece44133f966a0622a31cf3ed110fbe83e094e","src/thread_pool/mod.rs":"392ad78a209826c4fb7257288dc082ace380220893d44559480045587e279202","src/thread_pool/test.rs":"657b1938993eb98fb5f3fd1d02a77728e37d0e833390b4ba82926b9107ce3170","src/unwind.rs":"7baa4511467d008b14856ea8de7ced92b9251c9df4854f69c81f7efc3cf0cd6c","tests/double_init_fail.rs":"8c208ce45e83ab1dfc5890353d5b2f06fc8005684ae622827a65d05abb35a072","tests/init_zero_threads.rs":"5c7f7e0e13e9ead3733253e30d6b52ac5ee66fd6c105999d096bdf31cfccaf95","tests/scope_join.rs":"56f570c4b6a01704aacf93e7f17f89fe0f40f46ed6f9ede517abfe9adaf91f83","tests/scoped_threadpool.rs":"24d1293fe65ad5f194bbff9d1ef0486c3440d0a3783f04eaaaae4929adef5cb8","tests/simple_panic.rs":"916d40d36c1a0fad3e1dfb31550f0672641feab4b03d480f039143dbe2f2445f","tests/stack_overflow_crash.rs":"87b962c66f301ac44f808d992d4e8b861305db0c282f256761a5075c9f018243","tests/use_current_thread.rs":"fe1b981e77e422e616c09502731a70fb2f1c023d2386ef32c9d47e5a6f5bc162"},"package":"5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed"}
{"files":{"Cargo.toml":"60f04aab82c6489c779cf19535fddf0ec1a673edc07f523c66c10996806d2fbe","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"0621878e61f0d0fda054bcbe02df75192c28bde1ecc8289cbd86aeba2dd72720","README.md":"7281273bea1d5fdc57731513cf9f0e3b911d06ac9905b03a8375a1324951c35b","build.rs":"fa31cb198b772600d100a7c403ddedccef637d2e6b2da431fa7f02ca41307fc6","src/broadcast/mod.rs":"2c9a84e7e6e5e8d8e23e28d6f2703825d7d6af59f0a16bc6125d5f0d25bd7598","src/broadcast/test.rs":"fe50fc868e67d855a9f71e078b0c3a7780e789652abb4b586accb4ccf035e872","src/compile_fail/mod.rs":"4d70256295bd64691a8c1994b323559cda1888e85f0b45ca55711541c257dcb6","src/compile_fail/quicksort_race1.rs":"35f498cda38f4eb6e00117f78ed68e0fe5a3fa61c25303d9c08a19bda345bc6c","src/compile_fail/quicksort_race2.rs":"cbb40030c7867cae34bb373b6ec5d97c2ac6de39bc917f47879b30eb423924bc","src/compile_fail/quicksort_race3.rs":"8403643e64c969306b1a9b1243378e6ccdd313b57e1878dbd31393618082fd35","src/compile_fail/rc_return.rs":"197894803d8df58fc8005d90c86b90cd98f1972f1a4b57438516a565df35903f","src/compile_fail/rc_upvar.rs":"42d110429621f407ef0dada1306dab116583d2c782a99894204dd8e0ccd2312f","src/compile_fail/scope_join_bad.rs":"892959949f77cadfc07458473e7a290301182027ca64428df5a8ce887be0892b","src/job.rs":"06de0c2add2e303b6383bf11f5f0d75775c1efe6aa7bc16de3992117f1012f09","src/join/mod.rs":"7638c0fc1da1a2d2b14673c8a2e0f87d26c24232cebee26fd334bdc2caa80886","src/join/test.rs":"157db5306e8df89a8eea19dbba499f26c2f44d9803cb36a796c852a9a695821e","src/latch.rs":"3f5c73cc6998d7cea177c19a3c7ce0d4a5667c65d77f744a2159872f09e8fba6","src/lib.rs":"d34f6cbaba40293d3b3e095bd2eae989cce3467fe686835a37e80652ca8133df","src/private.rs":"152f6d65ce4741616a1dec796b9442f78a018d38bb040f76c4cd85008333a3bb","src/registry.rs":"bcfedc6d51eade2bedc30271ab8b21e98700e45f74bd8967d02ae6fe17f73a94","src/scope/mod.rs":"421a5561093928b1d0081d34c2bff78377055d8f6de0689088f52fe476d3a56a","src/scope/test.rs":"d4f068cae4ee4483b41bd3054582d96e74ced46eb57361e7510ef62d4318d340","src/sleep/README.md":"e1ac1a5556cf257f38b7654feb0615c208d9186fefbe52a584d4fe6545d7c373","src/sleep/counters.rs":"e9eccc7d76d17415156c12d30cc7bf89a5c64ca5742965bb4e6c1ce23c2782e9","src/sleep/mod.rs":"7576bc0a54c30f35b10c63a6fd23f2e33774ba994352634ea1ca7865818fb2a4","src/spawn/mod.rs":"087d2a3e5fd5dd22a780c14d71509f9eea0bac92ccfa35d9933c0b39b3e269e1","src/spawn/test.rs":"a28f8943f28a4cef642b6429c538b1df879c9eb1db9927ce69b97c686bf81173","src/test.rs":"7d0dee06fcf41bddf77449a85cece44133f966a0622a31cf3ed110fbe83e094e","src/thread_pool/mod.rs":"4c85b46eb8ba05631b658996f0d6c64247a05575c89621e0a54a1fc056cc447f","src/thread_pool/test.rs":"657b1938993eb98fb5f3fd1d02a77728e37d0e833390b4ba82926b9107ce3170","src/unwind.rs":"7baa4511467d008b14856ea8de7ced92b9251c9df4854f69c81f7efc3cf0cd6c","tests/double_init_fail.rs":"8c208ce45e83ab1dfc5890353d5b2f06fc8005684ae622827a65d05abb35a072","tests/init_zero_threads.rs":"5c7f7e0e13e9ead3733253e30d6b52ac5ee66fd6c105999d096bdf31cfccaf95","tests/scope_join.rs":"56f570c4b6a01704aacf93e7f17f89fe0f40f46ed6f9ede517abfe9adaf91f83","tests/scoped_threadpool.rs":"24d1293fe65ad5f194bbff9d1ef0486c3440d0a3783f04eaaaae4929adef5cb8","tests/simple_panic.rs":"916d40d36c1a0fad3e1dfb31550f0672641feab4b03d480f039143dbe2f2445f","tests/stack_overflow_crash.rs":"87b962c66f301ac44f808d992d4e8b861305db0c282f256761a5075c9f018243","tests/use_current_thread.rs":"fe1b981e77e422e616c09502731a70fb2f1c023d2386ef32c9d47e5a6f5bc162"},"package":"1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"}

View File

@@ -13,7 +13,7 @@
edition = "2021"
rust-version = "1.63"
name = "rayon-core"
version = "1.12.0"
version = "1.12.1"
authors = [
"Niko Matsakis <niko@alum.mit.edu>",
"Josh Stone <cuviper@gmail.com>",
@@ -68,6 +68,10 @@ version = "0.8.1"
[dependencies.crossbeam-utils]
version = "0.8.0"
[dependencies.wasm_sync]
version = "0.1.0"
optional = true
[dev-dependencies.rand]
version = "0.8"
@@ -77,5 +81,8 @@ version = "0.3"
[dev-dependencies.scoped-tls]
version = "1.0"
[features]
web_spin_lock = ["dep:wasm_sync"]
[target."cfg(unix)".dev-dependencies.libc]
version = "0.2"

View File

@@ -1,10 +1,11 @@
use std::marker::PhantomData;
use std::ops::Deref;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Condvar, Mutex};
use std::sync::Arc;
use std::usize;
use crate::registry::{Registry, WorkerThread};
use crate::sync::{Condvar, Mutex};
/// We define various kinds of latches, which are all a primitive signaling
/// mechanism. A latch starts as false. Eventually someone calls `set()` and

View File

@@ -103,6 +103,12 @@ pub use self::thread_pool::current_thread_index;
pub use self::thread_pool::ThreadPool;
pub use self::thread_pool::{yield_local, yield_now, Yield};
#[cfg(not(feature = "web_spin_lock"))]
use std::sync;
#[cfg(feature = "web_spin_lock")]
use wasm_sync as sync;
use self::registry::{CustomSpawn, DefaultSpawn, ThreadSpawn};
/// Returns the maximum number of threads that Rayon supports in a single thread-pool.

View File

@@ -1,6 +1,7 @@
use crate::job::{JobFifo, JobRef, StackJob};
use crate::latch::{AsCoreLatch, CoreLatch, Latch, LatchRef, LockLatch, OnceLatch, SpinLatch};
use crate::sleep::Sleep;
use crate::sync::Mutex;
use crate::unwind;
use crate::{
ErrorKind, ExitHandler, PanicHandler, StartHandler, ThreadPoolBuildError, ThreadPoolBuilder,
@@ -15,7 +16,7 @@ use std::io;
use std::mem;
use std::ptr;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex, Once};
use std::sync::{Arc, Once};
use std::thread;
use std::usize;

View File

@@ -2,9 +2,9 @@
//! for an overview.
use crate::latch::CoreLatch;
use crate::sync::{Condvar, Mutex};
use crossbeam_utils::CachePadded;
use std::sync::atomic::Ordering;
use std::sync::{Condvar, Mutex};
use std::thread;
use std::usize;

View File

@@ -4,8 +4,8 @@ use crate::unwind;
use std::mem;
use std::sync::Arc;
/// Fires off a task into the Rayon threadpool in the "static" or
/// "global" scope. Just like a standard thread, this task is not
/// Puts the task into the Rayon threadpool's job queue in the "static"
/// or "global" scope. Just like a standard thread, this task is not
/// tied to the current stack frame, and hence it cannot hold any
/// references other than those with `'static` lifetime. If you want
/// to spawn a task that references stack data, use [the `scope()`

View File

@@ -80,6 +80,43 @@ impl ThreadPool {
/// thread-local data from the current thread will not be
/// accessible.
///
/// # Warning: execution order
///
/// If the current thread is part of a different thread pool, it will try to
/// keep busy while the `op` completes in its target pool, similar to
/// calling [`ThreadPool::yield_now()`] in a loop. Therefore, it may
/// potentially schedule other tasks to run on the current thread in the
/// meantime. For example
///
/// ```rust
/// # use rayon_core as rayon;
/// fn main() {
/// rayon::ThreadPoolBuilder::new().num_threads(1).build_global().unwrap();
/// let pool = rayon_core::ThreadPoolBuilder::default().build().unwrap();
/// let do_it = || {
/// print!("one ");
/// pool.install(||{});
/// print!("two ");
/// };
/// rayon::join(|| do_it(), || do_it());
/// }
/// ```
///
/// Since we configured just one thread in the global pool, one might
/// expect `do_it()` to run sequentially, producing:
///
/// ```ascii
/// one two one two
/// ```
///
/// However each call to `install()` yields implicitly, allowing rayon to
/// run multiple instances of `do_it()` concurrently on the single, global
/// thread. The following output would be equally valid:
///
/// ```ascii
/// one one two two
/// ```
///
/// # Panics
///
/// If `op` should panic, that panic will be propagated.

File diff suppressed because one or more lines are too long

View File

@@ -11,9 +11,9 @@
[package]
edition = "2021"
rust-version = "1.56"
rust-version = "1.63"
name = "rayon"
version = "1.6.1"
version = "1.10.0"
authors = [
"Niko Matsakis <niko@alum.mit.edu>",
"Josh Stone <cuviper@gmail.com>",
@@ -22,7 +22,6 @@ exclude = [
"/ci/*",
"/scripts/*",
"/.github/*",
"/bors.toml",
]
description = "Simple work-stealing parallelism for Rust"
documentation = "https://docs.rs/rayon/"
@@ -43,13 +42,20 @@ version = "1.0"
default-features = false
[dependencies.rayon-core]
version = "1.10.0"
version = "1.12.1"
[dev-dependencies.lazy_static]
version = "1"
[dependencies.wasm_sync]
version = "0.1.0"
optional = true
[dev-dependencies.rand]
version = "0.8"
[dev-dependencies.rand_xorshift]
version = "0.3"
[features]
web_spin_lock = [
"dep:wasm_sync",
"rayon-core/web_spin_lock",
]

View File

@@ -2,8 +2,8 @@
[![Rayon crate](https://img.shields.io/crates/v/rayon.svg)](https://crates.io/crates/rayon)
[![Rayon documentation](https://docs.rs/rayon/badge.svg)](https://docs.rs/rayon)
![minimum rustc 1.56](https://img.shields.io/badge/rustc-1.56+-red.svg)
[![build status](https://github.com/rayon-rs/rayon/workflows/master/badge.svg)](https://github.com/rayon-rs/rayon/actions)
![minimum rustc 1.63](https://img.shields.io/badge/rustc-1.63+-red.svg)
[![build status](https://github.com/rayon-rs/rayon/workflows/main/badge.svg)](https://github.com/rayon-rs/rayon/actions)
[![Join the chat at https://gitter.im/rayon-rs/Lobby](https://badges.gitter.im/rayon-rs/Lobby.svg)](https://gitter.im/rayon-rs/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Rayon is a data-parallelism library for Rust. It is extremely
@@ -13,7 +13,7 @@ enjoy [this blog post][blog] about Rayon, which gives more background
and details about how it works, or [this video][video], from the Rust
Belt Rust conference.) Rayon is
[available on crates.io](https://crates.io/crates/rayon), and
[API Documentation is available on docs.rs](https://docs.rs/rayon/).
[API documentation is available on docs.rs](https://docs.rs/rayon).
[blog]: https://smallcultfollowing.com/babysteps/blog/2015/12/18/rayon-data-parallelism-in-rust/
[video]: https://www.youtube.com/watch?v=gof_OEv71Aw
@@ -71,12 +71,12 @@ as:
```toml
[dependencies]
rayon = "1.5"
rayon = "1.8"
```
To use the Parallel Iterator APIs, a number of traits have to be in
To use the parallel iterator APIs, a number of traits have to be in
scope. The easiest way to bring those things into scope is to use the
[Rayon prelude](https://docs.rs/rayon/*/rayon/prelude/index.html). In
[Rayon prelude](https://docs.rs/rayon/*/rayon/prelude/index.html). In
each module where you would like to use the parallel iterator APIs,
just add:
@@ -84,26 +84,44 @@ just add:
use rayon::prelude::*;
```
Rayon currently requires `rustc 1.56.0` or greater.
Rayon currently requires `rustc 1.63.0` or greater.
### Usage with WebAssembly
Rayon can work on the Web via WebAssembly, but requires an adapter
and some project configuration to account for differences between
WebAssembly threads and threads on the other platforms.
By default, when building to WebAssembly, Rayon will treat it as any
other platform without multithreading support and will fall back to
sequential iteration. This allows existing code to compile and run
successfully with no changes necessary, but it will run slower as it
will only use a single CPU core.
Check out [wasm-bindgen-rayon](https://github.com/GoogleChromeLabs/wasm-bindgen-rayon)
You can build Rayon-based projects with proper multithreading support
for the Web, but you'll need an adapter and some project configuration
to account for differences between WebAssembly threads and threads
on the other platforms.
Check out the
[wasm-bindgen-rayon](https://github.com/RReverser/wasm-bindgen-rayon)
docs for more details.
## Contribution
Rayon is an open source project! If you'd like to contribute to Rayon, check out [the list of "help wanted" issues](https://github.com/rayon-rs/rayon/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22). These are all (or should be) issues that are suitable for getting started, and they generally include a detailed set of instructions for what to do. Please ask questions if anything is unclear! Also, check out the [Guide to Development](https://github.com/rayon-rs/rayon/wiki/Guide-to-Development) page on the wiki. Note that all code submitted in PRs to Rayon is assumed to [be licensed under Rayon's dual MIT/Apache2 licensing](https://github.com/rayon-rs/rayon/blob/master/README.md#license).
Rayon is an open source project! If you'd like to contribute to Rayon,
check out
[the list of "help wanted" issues](https://github.com/rayon-rs/rayon/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22).
These are all (or should be) issues that are suitable for getting
started, and they generally include a detailed set of instructions for
what to do. Please ask questions if anything is unclear! Also, check
out the
[Guide to Development](https://github.com/rayon-rs/rayon/wiki/Guide-to-Development)
page on the wiki. Note that all code submitted in PRs to Rayon is
assumed to
[be licensed under Rayon's dual MIT/Apache 2.0 licensing](https://github.com/rayon-rs/rayon/blob/main/README.md#license).
## Quick demo
To see Rayon in action, check out the `rayon-demo` directory, which
includes a number of demos of code using Rayon. For example, run this
command to get a visualization of an nbody simulation. To see the
command to get a visualization of an N-body simulation. To see the
effect of using Rayon, press `s` to run sequentially and `p` to run in
parallel.
@@ -123,7 +141,7 @@ For more information on demos, try:
See [the Rayon FAQ][faq].
[faq]: https://github.com/rayon-rs/rayon/blob/master/FAQ.md
[faq]: https://github.com/rayon-rs/rayon/blob/main/FAQ.md
## License

View File

@@ -1,3 +1,69 @@
# Release rayon 1.10.0 (2024-03-23)
- The new methods `ParallelSlice::par_chunk_by` and
`ParallelSliceMut::par_chunk_by_mut` work like the slice methods `chunk_by`
and `chunk_by_mut` added in Rust 1.77.
# Release rayon 1.9.0 (2024-02-27)
- The new methods `IndexedParallelIterator::by_exponential_blocks` and
`by_uniform_blocks` allow processing items in smaller groups at a time.
- The new `iter::walk_tree`, `walk_tree_prefix`, and `walk_tree_postfix`
functions enable custom parallel iteration over tree-like structures.
- The new method `ParallelIterator::collect_vec_list` returns items as a linked
list of vectors, which is an efficient mode of parallel collection used by
many of the internal implementations of `collect`.
- The new methods `ParallelSliceMut::par_split_inclusive_mut`,
`ParallelSlice::par_split_inclusive`, and
`ParallelString::par_split_inclusive` all work like a normal split but
keeping the separator as part of the left slice.
- The new `ParallelString::par_split_ascii_whitespace` splits only on ASCII
whitespace, which is faster than including Unicode multi-byte whitespace.
- `OsString` now implements `FromParallelIterator<_>` and `ParallelExtend<_>`
for a few item types similar to the standard `FromIterator` and `Extend`.
- The internal `Pattern` trait for string methods is now implemented for
`[char; N]` and `&[char; N]`, matching any of the given characters.
# Release rayon 1.8.1 / rayon-core 1.12.1 (2024-01-17)
- The new `"web_spin_lock"` crate feature makes mutexes spin on the main
browser thread in WebAssembly, rather than suffer an error about forbidden
`atomics.wait` if they were to block in that context. Thanks @RReverser!
# Release rayon 1.8.0 / rayon-core 1.12.0 (2023-09-20)
- The minimum supported `rustc` is now 1.63.
- Added `ThreadPoolBuilder::use_current_thread` to use the builder thread as
part of the new thread pool. That thread does not run the pool's main loop,
but it may participate in work-stealing if it yields to rayon in some way.
- Implemented `FromParallelIterator<T>` for `Box<[T]>`, `Rc<[T]>`, and
`Arc<[T]>`, as well as `FromParallelIterator<Box<str>>` and
`ParallelExtend<Box<str>>` for `String`.
- `ThreadPoolBuilder::build_scoped` now uses `std::thread::scope`.
- The default number of threads is now determined using
`std::thread::available_parallelism` instead of the `num_cpus` crate.
- The internal logging facility has been removed, reducing bloat for all users.
- Many smaller performance tweaks and documentation updates.
# Release rayon 1.7.0 / rayon-core 1.11.0 (2023-03-03)
- The minimum supported `rustc` is now 1.59.
- Added a fallback when threading is unsupported.
- The new `ParallelIterator::take_any` and `skip_any` methods work like
unordered `IndexedParallelIterator::take` and `skip`, counting items in
whatever order they are visited in parallel.
- The new `ParallelIterator::take_any_while` and `skip_any_while` methods work
like unordered `Iterator::take_while` and `skip_while`, which previously had
no parallel equivalent. The "while" condition may be satisfied from anywhere
in the parallel iterator, affecting all future items regardless of position.
- The new `yield_now` and `yield_local` functions will cooperatively yield
execution to Rayon, either trying to execute pending work from the entire
pool or from just the local deques of the current thread, respectively.
# Release rayon-core 1.10.2 (2023-01-22)
- Fixed miri-reported UB for SharedReadOnly tags protected by a call.
# Release rayon 1.6.1 (2022-12-09)
- Simplified `par_bridge` to only pull one item at a time from the iterator,
@@ -303,8 +369,8 @@ Thanks to all of the contributors for this release!
- @seanchen1991
- @yegeun542
[RFC 1]: https://github.com/rayon-rs/rfcs/blob/master/accepted/rfc0001-scope-scheduling.md
[RFC 3]: https://github.com/rayon-rs/rfcs/blob/master/accepted/rfc0003-minimum-rustc.md
[RFC 1]: https://github.com/rayon-rs/rfcs/blob/main/accepted/rfc0001-scope-scheduling.md
[RFC 3]: https://github.com/rayon-rs/rfcs/blob/main/accepted/rfc0003-minimum-rustc.md
# Release rayon 1.0.3 (2018-11-02)

View File

@@ -78,7 +78,8 @@ impl<T: Send, const N: usize> IndexedParallelIterator for IntoIter<T, N> {
unsafe {
// Drain every item, and then the local array can just fall out of scope.
let mut array = ManuallyDrop::new(self.array);
callback.callback(DrainProducer::new(&mut *array))
let producer = DrainProducer::new(array.as_mut_slice());
callback.callback(producer)
}
}
}

View File

@@ -30,6 +30,8 @@ macro_rules! must_use {
}
must_use! {
by_exponential_blocks /** v.par_iter().by_exponential_blocks(); */
by_uniform_blocks /** v.par_iter().by_uniform_blocks(2); */
step_by /** v.par_iter().step_by(2); */
chain /** v.par_iter().chain(&v); */
chunks /** v.par_iter().chunks(2); */

View File

@@ -0,0 +1,131 @@
use super::plumbing::*;
use super::*;
struct BlocksCallback<S, C> {
sizes: S,
consumer: C,
len: usize,
}
impl<T, S, C> ProducerCallback<T> for BlocksCallback<S, C>
where
C: UnindexedConsumer<T>,
S: Iterator<Item = usize>,
{
type Output = C::Result;
fn callback<P: Producer<Item = T>>(mut self, mut producer: P) -> Self::Output {
let mut remaining_len = self.len;
let mut consumer = self.consumer;
// we need a local variable for the accumulated results
// we call the reducer's identity by splitting at 0
let (left_consumer, right_consumer, _) = consumer.split_at(0);
let mut leftmost_res = left_consumer.into_folder().complete();
consumer = right_consumer;
// now we loop on each block size
while remaining_len > 0 && !consumer.full() {
// we compute the next block's size
let size = self.sizes.next().unwrap_or(std::usize::MAX);
let capped_size = remaining_len.min(size);
remaining_len -= capped_size;
// split the producer
let (left_producer, right_producer) = producer.split_at(capped_size);
producer = right_producer;
// split the consumer
let (left_consumer, right_consumer, _) = consumer.split_at(capped_size);
consumer = right_consumer;
leftmost_res = consumer.to_reducer().reduce(
leftmost_res,
bridge_producer_consumer(capped_size, left_producer, left_consumer),
);
}
leftmost_res
}
}
/// `ExponentialBlocks` is a parallel iterator that consumes itself as a sequence
/// of parallel blocks of increasing sizes (exponentially).
///
/// This struct is created by the [`by_exponential_blocks()`] method on [`IndexedParallelIterator`]
///
/// [`by_exponential_blocks()`]: trait.IndexedParallelIterator.html#method.by_exponential_blocks
/// [`IndexedParallelIterator`]: trait.IndexedParallelIterator.html
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
#[derive(Debug, Clone)]
pub struct ExponentialBlocks<I> {
base: I,
}
impl<I> ExponentialBlocks<I> {
pub(super) fn new(base: I) -> Self {
Self { base }
}
}
impl<I> ParallelIterator for ExponentialBlocks<I>
where
I: IndexedParallelIterator,
{
type Item = I::Item;
fn drive_unindexed<C>(self, consumer: C) -> C::Result
where
C: UnindexedConsumer<Self::Item>,
{
let first = crate::current_num_threads();
let callback = BlocksCallback {
consumer,
sizes: std::iter::successors(Some(first), exponential_size),
len: self.base.len(),
};
self.base.with_producer(callback)
}
}
fn exponential_size(size: &usize) -> Option<usize> {
Some(size.saturating_mul(2))
}
/// `UniformBlocks` is a parallel iterator that consumes itself as a sequence
/// of parallel blocks of constant sizes.
///
/// This struct is created by the [`by_uniform_blocks()`] method on [`IndexedParallelIterator`]
///
/// [`by_uniform_blocks()`]: trait.IndexedParallelIterator.html#method.by_uniform_blocks
/// [`IndexedParallelIterator`]: trait.IndexedParallelIterator.html
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
#[derive(Debug, Clone)]
pub struct UniformBlocks<I> {
base: I,
block_size: usize,
}
impl<I> UniformBlocks<I> {
pub(super) fn new(base: I, block_size: usize) -> Self {
Self { base, block_size }
}
}
impl<I> ParallelIterator for UniformBlocks<I>
where
I: IndexedParallelIterator,
{
type Item = I::Item;
fn drive_unindexed<C>(self, consumer: C) -> C::Result
where
C: UnindexedConsumer<Self::Item>,
{
let callback = BlocksCallback {
consumer,
sizes: std::iter::repeat(self.block_size),
len: self.base.len(),
};
self.base.with_producer(callback)
}
}

View File

@@ -1,7 +1,6 @@
use super::plumbing::*;
use super::*;
use rayon_core::join;
use std::cmp;
use std::iter;
/// `Chain` is an iterator that joins `b` after `a` in one continuous iterator.
@@ -178,11 +177,11 @@ where
}
fn min_len(&self) -> usize {
cmp::max(self.a.min_len(), self.b.min_len())
Ord::max(self.a.min_len(), self.b.min_len())
}
fn max_len(&self) -> usize {
cmp::min(self.a.max_len(), self.b.max_len())
Ord::min(self.a.max_len(), self.b.max_len())
}
fn split_at(self, index: usize) -> (Self, Self) {

View File

@@ -1,5 +1,3 @@
use std::cmp::min;
use super::plumbing::*;
use super::*;
use crate::math::div_round_up;
@@ -133,7 +131,7 @@ where
}
fn split_at(self, index: usize) -> (Self, Self) {
let elem_index = min(index * self.chunk_size, self.len);
let elem_index = Ord::min(index * self.chunk_size, self.len);
let (left, right) = self.base.split_at(elem_index);
(
ChunkProducer {

View File

@@ -76,6 +76,7 @@ fn right_produces_items_with_no_complete() {
// Complete is not called by the consumer. Hence,the collection vector is not fully initialized.
#[test]
#[cfg_attr(not(panic = "unwind"), ignore)]
fn produces_items_with_no_complete() {
let counter = DropCounter::default();
let mut v = vec![];
@@ -273,6 +274,7 @@ fn right_panics() {
// The left consumer produces fewer items while the right
// consumer produces correct number; check that created elements are dropped
#[test]
#[cfg_attr(not(panic = "unwind"), ignore)]
fn left_produces_fewer_items_drops() {
let counter = DropCounter::default();
let mut v = vec![];

View File

@@ -2,26 +2,83 @@ use super::noop::NoopConsumer;
use super::plumbing::{Consumer, Folder, Reducer, UnindexedConsumer};
use super::{IntoParallelIterator, ParallelExtend, ParallelIterator};
use either::Either;
use std::borrow::Cow;
use std::collections::LinkedList;
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::collections::{BinaryHeap, VecDeque};
use std::ffi::{OsStr, OsString};
use std::hash::{BuildHasher, Hash};
/// Performs a generic `par_extend` by collecting to a `LinkedList<Vec<_>>` in
/// parallel, then extending the collection sequentially.
macro_rules! extend {
($self:ident, $par_iter:ident, $extend:ident) => {
$extend(
$self,
$par_iter.into_par_iter().drive_unindexed(ListVecConsumer),
);
($self:ident, $par_iter:ident) => {
extend!($self <- fast_collect($par_iter))
};
($self:ident <- $vecs:expr) => {
match $vecs {
Either::Left(vec) => $self.extend(vec),
Either::Right(list) => {
for vec in list {
$self.extend(vec);
}
}
}
};
}
macro_rules! extend_reserved {
($self:ident, $par_iter:ident, $len:ident) => {
let vecs = fast_collect($par_iter);
$self.reserve($len(&vecs));
extend!($self <- vecs)
};
($self:ident, $par_iter:ident) => {
extend_reserved!($self, $par_iter, len)
};
}
/// Computes the total length of a `LinkedList<Vec<_>>`.
fn len<T>(list: &LinkedList<Vec<T>>) -> usize {
list.iter().map(Vec::len).sum()
/// Computes the total length of a `fast_collect` result.
fn len<T>(vecs: &Either<Vec<T>, LinkedList<Vec<T>>>) -> usize {
match vecs {
Either::Left(vec) => vec.len(),
Either::Right(list) => list.iter().map(Vec::len).sum(),
}
}
/// Computes the total string length of a `fast_collect` result.
fn string_len<T: AsRef<str>>(vecs: &Either<Vec<T>, LinkedList<Vec<T>>>) -> usize {
let strs = match vecs {
Either::Left(vec) => Either::Left(vec.iter()),
Either::Right(list) => Either::Right(list.iter().flatten()),
};
strs.map(AsRef::as_ref).map(str::len).sum()
}
/// Computes the total OS-string length of a `fast_collect` result.
fn osstring_len<T: AsRef<OsStr>>(vecs: &Either<Vec<T>, LinkedList<Vec<T>>>) -> usize {
let osstrs = match vecs {
Either::Left(vec) => Either::Left(vec.iter()),
Either::Right(list) => Either::Right(list.iter().flatten()),
};
osstrs.map(AsRef::as_ref).map(OsStr::len).sum()
}
pub(super) fn fast_collect<I, T>(pi: I) -> Either<Vec<T>, LinkedList<Vec<T>>>
where
I: IntoParallelIterator<Item = T>,
T: Send,
{
let par_iter = pi.into_par_iter();
match par_iter.opt_len() {
Some(len) => {
// Pseudo-specialization. See impl of ParallelExtend for Vec for more details.
let mut vec = Vec::new();
super::collect::special_extend(par_iter, len, &mut vec);
Either::Left(vec)
}
None => Either::Right(par_iter.drive_unindexed(ListVecConsumer)),
}
}
struct ListVecConsumer;
@@ -87,16 +144,6 @@ impl<T> Folder<T> for ListVecFolder<T> {
}
}
fn heap_extend<T, Item>(heap: &mut BinaryHeap<T>, list: LinkedList<Vec<Item>>)
where
BinaryHeap<T>: Extend<Item>,
{
heap.reserve(len(&list));
for vec in list {
heap.extend(vec);
}
}
/// Extends a binary heap with items from a parallel iterator.
impl<T> ParallelExtend<T> for BinaryHeap<T>
where
@@ -106,7 +153,7 @@ where
where
I: IntoParallelIterator<Item = T>,
{
extend!(self, par_iter, heap_extend);
extend_reserved!(self, par_iter);
}
}
@@ -119,16 +166,7 @@ where
where
I: IntoParallelIterator<Item = &'a T>,
{
extend!(self, par_iter, heap_extend);
}
}
fn btree_map_extend<K, V, Item>(map: &mut BTreeMap<K, V>, list: LinkedList<Vec<Item>>)
where
BTreeMap<K, V>: Extend<Item>,
{
for vec in list {
map.extend(vec);
extend_reserved!(self, par_iter);
}
}
@@ -142,7 +180,7 @@ where
where
I: IntoParallelIterator<Item = (K, V)>,
{
extend!(self, par_iter, btree_map_extend);
extend!(self, par_iter);
}
}
@@ -156,16 +194,7 @@ where
where
I: IntoParallelIterator<Item = (&'a K, &'a V)>,
{
extend!(self, par_iter, btree_map_extend);
}
}
fn btree_set_extend<T, Item>(set: &mut BTreeSet<T>, list: LinkedList<Vec<Item>>)
where
BTreeSet<T>: Extend<Item>,
{
for vec in list {
set.extend(vec);
extend!(self, par_iter);
}
}
@@ -178,7 +207,7 @@ where
where
I: IntoParallelIterator<Item = T>,
{
extend!(self, par_iter, btree_set_extend);
extend!(self, par_iter);
}
}
@@ -191,19 +220,7 @@ where
where
I: IntoParallelIterator<Item = &'a T>,
{
extend!(self, par_iter, btree_set_extend);
}
}
fn hash_map_extend<K, V, S, Item>(map: &mut HashMap<K, V, S>, list: LinkedList<Vec<Item>>)
where
HashMap<K, V, S>: Extend<Item>,
K: Eq + Hash,
S: BuildHasher,
{
map.reserve(len(&list));
for vec in list {
map.extend(vec);
extend!(self, par_iter);
}
}
@@ -219,7 +236,7 @@ where
I: IntoParallelIterator<Item = (K, V)>,
{
// See the map_collect benchmarks in rayon-demo for different strategies.
extend!(self, par_iter, hash_map_extend);
extend_reserved!(self, par_iter);
}
}
@@ -234,19 +251,7 @@ where
where
I: IntoParallelIterator<Item = (&'a K, &'a V)>,
{
extend!(self, par_iter, hash_map_extend);
}
}
fn hash_set_extend<T, S, Item>(set: &mut HashSet<T, S>, list: LinkedList<Vec<Item>>)
where
HashSet<T, S>: Extend<Item>,
T: Eq + Hash,
S: BuildHasher,
{
set.reserve(len(&list));
for vec in list {
set.extend(vec);
extend_reserved!(self, par_iter);
}
}
@@ -260,7 +265,7 @@ where
where
I: IntoParallelIterator<Item = T>,
{
extend!(self, par_iter, hash_set_extend);
extend_reserved!(self, par_iter);
}
}
@@ -274,7 +279,7 @@ where
where
I: IntoParallelIterator<Item = &'a T>,
{
extend!(self, par_iter, hash_set_extend);
extend_reserved!(self, par_iter);
}
}
@@ -375,9 +380,34 @@ impl<T> Reducer<LinkedList<T>> for ListReducer {
}
}
fn flat_string_extend(string: &mut String, list: LinkedList<String>) {
string.reserve(list.iter().map(String::len).sum());
string.extend(list);
/// Extends an OS-string with string slices from a parallel iterator.
impl<'a> ParallelExtend<&'a OsStr> for OsString {
fn par_extend<I>(&mut self, par_iter: I)
where
I: IntoParallelIterator<Item = &'a OsStr>,
{
extend_reserved!(self, par_iter, osstring_len);
}
}
/// Extends an OS-string with strings from a parallel iterator.
impl ParallelExtend<OsString> for OsString {
fn par_extend<I>(&mut self, par_iter: I)
where
I: IntoParallelIterator<Item = OsString>,
{
extend_reserved!(self, par_iter, osstring_len);
}
}
/// Extends an OS-string with string slices from a parallel iterator.
impl<'a> ParallelExtend<Cow<'a, OsStr>> for OsString {
fn par_extend<I>(&mut self, par_iter: I)
where
I: IntoParallelIterator<Item = Cow<'a, OsStr>>,
{
extend_reserved!(self, par_iter, osstring_len);
}
}
/// Extends a string with characters from a parallel iterator.
@@ -389,7 +419,8 @@ impl ParallelExtend<char> for String {
// This is like `extend`, but `Vec<char>` is less efficient to deal
// with than `String`, so instead collect to `LinkedList<String>`.
let list = par_iter.into_par_iter().drive_unindexed(ListStringConsumer);
flat_string_extend(self, list);
self.reserve(list.iter().map(String::len).sum());
self.extend(list);
}
}
@@ -468,25 +499,13 @@ impl Folder<char> for ListStringFolder {
}
}
fn string_extend<Item>(string: &mut String, list: LinkedList<Vec<Item>>)
where
String: Extend<Item>,
Item: AsRef<str>,
{
let len = list.iter().flatten().map(Item::as_ref).map(str::len).sum();
string.reserve(len);
for vec in list {
string.extend(vec);
}
}
/// Extends a string with string slices from a parallel iterator.
impl<'a> ParallelExtend<&'a str> for String {
fn par_extend<I>(&mut self, par_iter: I)
where
I: IntoParallelIterator<Item = &'a str>,
{
extend!(self, par_iter, string_extend);
extend_reserved!(self, par_iter, string_len);
}
}
@@ -496,7 +515,17 @@ impl ParallelExtend<String> for String {
where
I: IntoParallelIterator<Item = String>,
{
extend!(self, par_iter, string_extend);
extend_reserved!(self, par_iter, string_len);
}
}
/// Extends a string with boxed strings from a parallel iterator.
impl ParallelExtend<Box<str>> for String {
fn par_extend<I>(&mut self, par_iter: I)
where
I: IntoParallelIterator<Item = Box<str>>,
{
extend_reserved!(self, par_iter, string_len);
}
}
@@ -506,17 +535,7 @@ impl<'a> ParallelExtend<Cow<'a, str>> for String {
where
I: IntoParallelIterator<Item = Cow<'a, str>>,
{
extend!(self, par_iter, string_extend);
}
}
fn deque_extend<T, Item>(deque: &mut VecDeque<T>, list: LinkedList<Vec<Item>>)
where
VecDeque<T>: Extend<Item>,
{
deque.reserve(len(&list));
for vec in list {
deque.extend(vec);
extend_reserved!(self, par_iter, string_len);
}
}
@@ -529,7 +548,7 @@ where
where
I: IntoParallelIterator<Item = T>,
{
extend!(self, par_iter, deque_extend);
extend_reserved!(self, par_iter);
}
}
@@ -542,14 +561,7 @@ where
where
I: IntoParallelIterator<Item = &'a T>,
{
extend!(self, par_iter, deque_extend);
}
}
fn vec_append<T>(vec: &mut Vec<T>, list: LinkedList<Vec<T>>) {
vec.reserve(len(&list));
for mut other in list {
vec.append(&mut other);
extend_reserved!(self, par_iter);
}
}
@@ -574,7 +586,10 @@ where
None => {
// This works like `extend`, but `Vec::append` is more efficient.
let list = par_iter.drive_unindexed(ListVecConsumer);
vec_append(self, list);
self.reserve(list.iter().map(Vec::len).sum());
for mut other in list {
self.append(&mut other);
}
}
}
}

View File

@@ -181,25 +181,17 @@ impl<'p, P: 'p + Fn(&T) -> bool, T> Folder<T> for FindFolder<'p, T, P> {
};
if !found_best_in_range && (self.find_op)(&item) {
// Continuously try to set best_found until we succeed or we
// discover a better match was already found.
let mut current = self.best_found.load(Ordering::Relaxed);
loop {
if better_position(current, self.boundary, self.match_position) {
break;
}
match self.best_found.compare_exchange_weak(
current,
self.boundary,
Ordering::Relaxed,
Ordering::Relaxed,
) {
Ok(_) => {
self.item = Some(item);
break;
}
Err(v) => current = v,
}
// Update the best found index if ours is better.
let update =
self.best_found
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
better_position(self.boundary, current, self.match_position)
.then_some(self.boundary)
});
// Save this item if our index was better or equal.
if update.is_ok() || update == Err(self.boundary) {
self.item = Some(item);
}
}
self

View File

@@ -1,5 +1,4 @@
use super::*;
use std::sync::atomic::AtomicUsize;
#[test]
fn same_range_first_consumers_return_correct_answer() {

View File

@@ -201,16 +201,16 @@ mod test {
assert_eq!(4, (0..8).into_par_iter().fold_chunks(2, id, sum).len());
assert_eq!(3, (0..9).into_par_iter().fold_chunks(3, id, sum).len());
assert_eq!(3, (0..8).into_par_iter().fold_chunks(3, id, sum).len());
assert_eq!(1, (&[1]).par_iter().fold_chunks(3, id, sum).len());
assert_eq!(1, [1].par_iter().fold_chunks(3, id, sum).len());
assert_eq!(0, (0..0).into_par_iter().fold_chunks(3, id, sum).len());
}
#[test]
fn check_fold_chunks_uneven() {
let cases: Vec<(Vec<u32>, usize, Vec<u32>)> = vec![
((0..5).collect(), 3, vec![0 + 1 + 2, 3 + 4]),
((0..5).collect(), 3, vec![1 + 2, 3 + 4]),
(vec![1], 5, vec![1]),
((0..4).collect(), 3, vec![0 + 1 + 2, 3]),
((0..4).collect(), 3, vec![1 + 2, 3]),
];
for (i, (v, n, expected)) in cases.into_iter().enumerate() {

View File

@@ -196,16 +196,16 @@ mod test {
assert_eq!(4, (0..8).into_par_iter().fold_chunks_with(2, 0, sum).len());
assert_eq!(3, (0..9).into_par_iter().fold_chunks_with(3, 0, sum).len());
assert_eq!(3, (0..8).into_par_iter().fold_chunks_with(3, 0, sum).len());
assert_eq!(1, (&[1]).par_iter().fold_chunks_with(3, 0, sum).len());
assert_eq!(1, [1].par_iter().fold_chunks_with(3, 0, sum).len());
assert_eq!(0, (0..0).into_par_iter().fold_chunks_with(3, 0, sum).len());
}
#[test]
fn check_fold_chunks_uneven() {
let cases: Vec<(Vec<u32>, usize, Vec<u32>)> = vec![
((0..5).collect(), 3, vec![0 + 1 + 2, 3 + 4]),
((0..5).collect(), 3, vec![1 + 2, 3 + 4]),
(vec![1], 5, vec![1]),
((0..4).collect(), 3, vec![0 + 1 + 2, 3]),
((0..4).collect(), 3, vec![1 + 2, 3]),
];
for (i, (v, n, expected)) in cases.into_iter().enumerate() {

View File

@@ -5,7 +5,10 @@ use std::borrow::Cow;
use std::collections::LinkedList;
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::collections::{BinaryHeap, VecDeque};
use std::ffi::{OsStr, OsString};
use std::hash::{BuildHasher, Hash};
use std::rc::Rc;
use std::sync::Arc;
/// Creates an empty default collection and extends it.
fn collect_extended<C, I>(par_iter: I) -> C
@@ -31,6 +34,45 @@ where
}
}
/// Collects items from a parallel iterator into a boxed slice.
impl<T> FromParallelIterator<T> for Box<[T]>
where
T: Send,
{
fn from_par_iter<I>(par_iter: I) -> Self
where
I: IntoParallelIterator<Item = T>,
{
Vec::from_par_iter(par_iter).into()
}
}
/// Collects items from a parallel iterator into a reference-counted slice.
impl<T> FromParallelIterator<T> for Rc<[T]>
where
T: Send,
{
fn from_par_iter<I>(par_iter: I) -> Self
where
I: IntoParallelIterator<Item = T>,
{
Vec::from_par_iter(par_iter).into()
}
}
/// Collects items from a parallel iterator into an atomically-reference-counted slice.
impl<T> FromParallelIterator<T> for Arc<[T]>
where
T: Send,
{
fn from_par_iter<I>(par_iter: I) -> Self
where
I: IntoParallelIterator<Item = T>,
{
Vec::from_par_iter(par_iter).into()
}
}
/// Collects items from a parallel iterator into a vecdeque.
impl<T> FromParallelIterator<T> for VecDeque<T>
where
@@ -174,6 +216,16 @@ impl FromParallelIterator<String> for String {
}
}
/// Collects boxed strings from a parallel iterator into one large string.
impl FromParallelIterator<Box<str>> for String {
fn from_par_iter<I>(par_iter: I) -> Self
where
I: IntoParallelIterator<Item = Box<str>>,
{
collect_extended(par_iter)
}
}
/// Collects string slices from a parallel iterator into a string.
impl<'a> FromParallelIterator<Cow<'a, str>> for String {
fn from_par_iter<I>(par_iter: I) -> Self
@@ -184,6 +236,36 @@ impl<'a> FromParallelIterator<Cow<'a, str>> for String {
}
}
/// Collects OS-string slices from a parallel iterator into an OS-string.
impl<'a> FromParallelIterator<&'a OsStr> for OsString {
fn from_par_iter<I>(par_iter: I) -> Self
where
I: IntoParallelIterator<Item = &'a OsStr>,
{
collect_extended(par_iter)
}
}
/// Collects OS-strings from a parallel iterator into one large OS-string.
impl FromParallelIterator<OsString> for OsString {
fn from_par_iter<I>(par_iter: I) -> Self
where
I: IntoParallelIterator<Item = OsString>,
{
collect_extended(par_iter)
}
}
/// Collects OS-string slices from a parallel iterator into an OS-string.
impl<'a> FromParallelIterator<Cow<'a, OsStr>> for OsString {
fn from_par_iter<I>(par_iter: I) -> Self
where
I: IntoParallelIterator<Item = Cow<'a, OsStr>>,
{
collect_extended(par_iter)
}
}
/// Collects an arbitrary `Cow` collection.
///
/// Note, the standard library only has `FromIterator` for `Cow<'a, str>` and

View File

@@ -1,6 +1,5 @@
use super::plumbing::*;
use super::*;
use std::cmp;
use std::iter::Fuse;
/// `Interleave` is an iterator that interleaves elements of iterators
@@ -185,11 +184,11 @@ where
}
fn min_len(&self) -> usize {
cmp::max(self.i.min_len(), self.j.min_len())
Ord::max(self.i.min_len(), self.j.min_len())
}
fn max_len(&self) -> usize {
cmp::min(self.i.max_len(), self.j.max_len())
Ord::min(self.i.max_len(), self.j.max_len())
}
/// We know 0 < index <= self.i_len + self.j_len

View File

@@ -1,6 +1,5 @@
use super::plumbing::*;
use super::*;
use std::cmp;
/// `MinLen` is an iterator that imposes a minimum length on iterator splits.
/// This struct is created by the [`with_min_len()`] method on [`IndexedParallelIterator`]
@@ -107,7 +106,7 @@ where
}
fn min_len(&self) -> usize {
cmp::max(self.min, self.base.min_len())
Ord::max(self.min, self.base.min_len())
}
fn max_len(&self) -> usize {
@@ -245,7 +244,7 @@ where
}
fn max_len(&self) -> usize {
cmp::min(self.max, self.base.max_len())
Ord::min(self.max, self.base.max_len())
}
fn split_at(self, index: usize) -> (Self, Self) {

View File

@@ -82,7 +82,8 @@
use self::plumbing::*;
use self::private::Try;
pub use either::Either;
use std::cmp::{self, Ordering};
use std::cmp::Ordering;
use std::collections::LinkedList;
use std::iter::{Product, Sum};
use std::ops::{Fn, RangeBounds};
@@ -102,6 +103,7 @@ mod test;
// e.g. `find::find()`, are always used **prefixed**, so that they
// can be readily distinguished.
mod blocks;
mod chain;
mod chunks;
mod cloned;
@@ -141,20 +143,26 @@ mod reduce;
mod repeat;
mod rev;
mod skip;
mod skip_any;
mod skip_any_while;
mod splitter;
mod step_by;
mod sum;
mod take;
mod take_any;
mod take_any_while;
mod try_fold;
mod try_reduce;
mod try_reduce_with;
mod unzip;
mod update;
mod walk_tree;
mod while_some;
mod zip;
mod zip_eq;
pub use self::{
blocks::{ExponentialBlocks, UniformBlocks},
chain::Chain,
chunks::Chunks,
cloned::Cloned,
@@ -185,11 +193,18 @@ pub use self::{
repeat::{repeat, repeatn, Repeat, RepeatN},
rev::Rev,
skip::Skip,
skip_any::SkipAny,
skip_any_while::SkipAnyWhile,
splitter::{split, Split},
step_by::StepBy,
take::Take,
take_any::TakeAny,
take_any_while::TakeAnyWhile,
try_fold::{TryFold, TryFoldWith},
update::Update,
walk_tree::{
walk_tree, walk_tree_postfix, walk_tree_prefix, WalkTree, WalkTreePostfix, WalkTreePrefix,
},
while_some::WhileSome,
zip::Zip,
zip_eq::ZipEq,
@@ -1417,7 +1432,7 @@ pub trait ParallelIterator: Sized + Send {
/// specified, so if the `Ord` impl is not truly associative, then
/// the results are not deterministic.
///
/// Basically equivalent to `self.reduce_with(|a, b| cmp::min(a, b))`.
/// Basically equivalent to `self.reduce_with(|a, b| Ord::min(a, b))`.
///
/// # Examples
///
@@ -1436,7 +1451,7 @@ pub trait ParallelIterator: Sized + Send {
where
Self::Item: Ord,
{
self.reduce_with(cmp::min)
self.reduce_with(Ord::min)
}
/// Computes the minimum of all the items in the iterator with respect to
@@ -1515,7 +1530,7 @@ pub trait ParallelIterator: Sized + Send {
/// specified, so if the `Ord` impl is not truly associative, then
/// the results are not deterministic.
///
/// Basically equivalent to `self.reduce_with(|a, b| cmp::max(a, b))`.
/// Basically equivalent to `self.reduce_with(|a, b| Ord::max(a, b))`.
///
/// # Examples
///
@@ -1534,12 +1549,12 @@ pub trait ParallelIterator: Sized + Send {
where
Self::Item: Ord,
{
self.reduce_with(cmp::max)
self.reduce_with(Ord::max)
}
/// Computes the maximum of all the items in the iterator with respect to
/// the given comparison function. If the iterator is empty, `None` is
/// returned; otherwise, `Some(min)` is returned.
/// returned; otherwise, `Some(max)` is returned.
///
/// Note that the order in which the items will be reduced is not
/// specified, so if the comparison function is not associative, then
@@ -1665,6 +1680,9 @@ pub trait ParallelIterator: Sized + Send {
/// will be stopped, while attempts to the left must continue in case
/// an earlier match is found.
///
/// For added performance, you might consider using `find_first` in conjunction with
/// [`by_exponential_blocks()`][IndexedParallelIterator::by_exponential_blocks].
///
/// Note that not all parallel iterators have a useful order, much like
/// sequential `HashMap` iteration, so "first" may be nebulous. If you
/// just want the first match that discovered anywhere in the iterator,
@@ -1953,6 +1971,9 @@ pub trait ParallelIterator: Sized + Send {
/// of how many elements the iterator contains, and even allows you to reuse
/// an existing vector's backing store rather than allocating a fresh vector.
///
/// See also [`collect_vec_list()`][Self::collect_vec_list] for collecting
/// into a `LinkedList<Vec<T>>`.
///
/// [`IndexedParallelIterator`]: trait.IndexedParallelIterator.html
/// [`collect_into_vec()`]:
/// trait.IndexedParallelIterator.html#method.collect_into_vec
@@ -2194,6 +2215,188 @@ pub trait ParallelIterator: Sized + Send {
Intersperse::new(self, element)
}
/// Creates an iterator that yields `n` elements from *anywhere* in the original iterator.
///
/// This is similar to [`IndexedParallelIterator::take`] without being
/// constrained to the "first" `n` of the original iterator order. The
/// taken items will still maintain their relative order where that is
/// visible in `collect`, `reduce`, and similar outputs.
///
/// # Examples
///
/// ```
/// use rayon::prelude::*;
///
/// let result: Vec<_> = (0..100)
/// .into_par_iter()
/// .filter(|&x| x % 2 == 0)
/// .take_any(5)
/// .collect();
///
/// assert_eq!(result.len(), 5);
/// assert!(result.windows(2).all(|w| w[0] < w[1]));
/// ```
fn take_any(self, n: usize) -> TakeAny<Self> {
TakeAny::new(self, n)
}
/// Creates an iterator that skips `n` elements from *anywhere* in the original iterator.
///
/// This is similar to [`IndexedParallelIterator::skip`] without being
/// constrained to the "first" `n` of the original iterator order. The
/// remaining items will still maintain their relative order where that is
/// visible in `collect`, `reduce`, and similar outputs.
///
/// # Examples
///
/// ```
/// use rayon::prelude::*;
///
/// let result: Vec<_> = (0..100)
/// .into_par_iter()
/// .filter(|&x| x % 2 == 0)
/// .skip_any(5)
/// .collect();
///
/// assert_eq!(result.len(), 45);
/// assert!(result.windows(2).all(|w| w[0] < w[1]));
/// ```
fn skip_any(self, n: usize) -> SkipAny<Self> {
SkipAny::new(self, n)
}
/// Creates an iterator that takes elements from *anywhere* in the original iterator
/// until the given `predicate` returns `false`.
///
/// The `predicate` may be anything -- e.g. it could be checking a fact about the item, a
/// global condition unrelated to the item itself, or some combination thereof.
///
/// If parallel calls to the `predicate` race and give different results, then the
/// `true` results will still take those particular items, while respecting the `false`
/// result from elsewhere to skip any further items.
///
/// This is similar to [`Iterator::take_while`] without being constrained to the original
/// iterator order. The taken items will still maintain their relative order where that is
/// visible in `collect`, `reduce`, and similar outputs.
///
/// # Examples
///
/// ```
/// use rayon::prelude::*;
///
/// let result: Vec<_> = (0..100)
/// .into_par_iter()
/// .take_any_while(|x| *x < 50)
/// .collect();
///
/// assert!(result.len() <= 50);
/// assert!(result.windows(2).all(|w| w[0] < w[1]));
/// ```
///
/// ```
/// use rayon::prelude::*;
/// use std::sync::atomic::AtomicUsize;
/// use std::sync::atomic::Ordering::Relaxed;
///
/// // Collect any group of items that sum <= 1000
/// let quota = AtomicUsize::new(1000);
/// let result: Vec<_> = (0_usize..100)
/// .into_par_iter()
/// .take_any_while(|&x| {
/// quota.fetch_update(Relaxed, Relaxed, |q| q.checked_sub(x))
/// .is_ok()
/// })
/// .collect();
///
/// let sum = result.iter().sum::<usize>();
/// assert!(matches!(sum, 902..=1000));
/// ```
fn take_any_while<P>(self, predicate: P) -> TakeAnyWhile<Self, P>
where
P: Fn(&Self::Item) -> bool + Sync + Send,
{
TakeAnyWhile::new(self, predicate)
}
/// Creates an iterator that skips elements from *anywhere* in the original iterator
/// until the given `predicate` returns `false`.
///
/// The `predicate` may be anything -- e.g. it could be checking a fact about the item, a
/// global condition unrelated to the item itself, or some combination thereof.
///
/// If parallel calls to the `predicate` race and give different results, then the
/// `true` results will still skip those particular items, while respecting the `false`
/// result from elsewhere to skip any further items.
///
/// This is similar to [`Iterator::skip_while`] without being constrained to the original
/// iterator order. The remaining items will still maintain their relative order where that is
/// visible in `collect`, `reduce`, and similar outputs.
///
/// # Examples
///
/// ```
/// use rayon::prelude::*;
///
/// let result: Vec<_> = (0..100)
/// .into_par_iter()
/// .skip_any_while(|x| *x < 50)
/// .collect();
///
/// assert!(result.len() >= 50);
/// assert!(result.windows(2).all(|w| w[0] < w[1]));
/// ```
fn skip_any_while<P>(self, predicate: P) -> SkipAnyWhile<Self, P>
where
P: Fn(&Self::Item) -> bool + Sync + Send,
{
SkipAnyWhile::new(self, predicate)
}
/// Collects this iterator into a linked list of vectors.
///
/// This is useful when you need to condense a parallel iterator into a collection,
/// but have no specific requirements for what that collection should be. If you
/// plan to store the collection longer-term, `Vec<T>` is, as always, likely the
/// best default choice, despite the overhead that comes from concatenating each
/// vector. Or, if this is an `IndexedParallelIterator`, you should also prefer to
/// just collect to a `Vec<T>`.
///
/// Internally, most [`FromParallelIterator`]/[`ParallelExtend`] implementations
/// use this strategy; each job collecting their chunk of the iterator to a `Vec<T>`
/// and those chunks getting merged into a `LinkedList`, before then extending the
/// collection with each vector. This is a very efficient way to collect an
/// unindexed parallel iterator, without much intermediate data movement.
///
/// # Examples
///
/// ```
/// # use std::collections::LinkedList;
/// use rayon::prelude::*;
///
/// let result: LinkedList<Vec<_>> = (0..=100)
/// .into_par_iter()
/// .filter(|x| x % 2 == 0)
/// .flat_map(|x| 0..x)
/// .collect_vec_list();
///
/// // `par_iter.collect_vec_list().into_iter().flatten()` turns
/// // a parallel iterator into a serial one
/// let total_len = result.into_iter().flatten().count();
/// assert_eq!(total_len, 2550);
/// ```
fn collect_vec_list(self) -> LinkedList<Vec<Self::Item>> {
match extend::fast_collect(self) {
Either::Left(vec) => {
let mut list = LinkedList::new();
if !vec.is_empty() {
list.push_back(vec);
}
list
}
Either::Right(list) => list,
}
}
/// Internal method used to define the behavior of this parallel
/// iterator. You should not need to call this directly.
///
@@ -2205,7 +2408,7 @@ pub trait ParallelIterator: Sized + Send {
/// See the [README] for more details on the internals of parallel
/// iterators.
///
/// [README]: https://github.com/rayon-rs/rayon/blob/master/src/iter/plumbing/README.md
/// [README]: https://github.com/rayon-rs/rayon/blob/main/src/iter/plumbing/README.md
fn drive_unindexed<C>(self, consumer: C) -> C::Result
where
C: UnindexedConsumer<Self::Item>;
@@ -2246,8 +2449,72 @@ impl<T: ParallelIterator> IntoParallelIterator for T {
// Waiting for `ExactSizeIterator::is_empty` to be stabilized. See rust-lang/rust#35428
#[allow(clippy::len_without_is_empty)]
pub trait IndexedParallelIterator: ParallelIterator {
/// Divides an iterator into sequential blocks of exponentially-increasing size.
///
/// Normally, parallel iterators are recursively divided into tasks in parallel.
/// This adaptor changes the default behavior by splitting the iterator into a **sequence**
/// of parallel iterators of increasing sizes.
/// Sizes grow exponentially in order to avoid creating
/// too many blocks. This also allows to balance the current block with all previous ones.
///
/// This can have many applications but the most notable ones are:
/// - better performance with [`find_first()`][ParallelIterator::find_first]
/// - more predictable performance with [`find_any()`][ParallelIterator::find_any]
/// or any interruptible computation
///
/// # Examples
///
/// ```
/// use rayon::prelude::*;
/// assert_eq!((0..10_000).into_par_iter()
/// .by_exponential_blocks()
/// .find_first(|&e| e==4_999), Some(4_999))
/// ```
///
/// In this example, without blocks, rayon will split the initial range into two but all work
/// on the right hand side (from 5,000 onwards) is **useless** since the sequential algorithm
/// never goes there. This means that if two threads are used there will be **no** speedup **at
/// all**.
///
/// `by_exponential_blocks` on the other hand will start with the leftmost range from 0
/// to `p` (threads number), continue with p to 3p, the 3p to 7p...
///
/// Each subrange is treated in parallel, while all subranges are treated sequentially.
/// We therefore ensure a logarithmic number of blocks (and overhead) while guaranteeing
/// we stop at the first block containing the searched data.
fn by_exponential_blocks(self) -> ExponentialBlocks<Self> {
ExponentialBlocks::new(self)
}
/// Divides an iterator into sequential blocks of the given size.
///
/// Normally, parallel iterators are recursively divided into tasks in parallel.
/// This adaptor changes the default behavior by splitting the iterator into a **sequence**
/// of parallel iterators of given `block_size`.
/// The main application is to obtain better
/// memory locality (especially if the reduce operation re-use folded data).
///
/// **Panics** if `block_size` is 0.
///
/// # Example
/// ```
/// use rayon::prelude::*;
/// // during most reductions v1 and v2 fit the cache
/// let v = (0u32..10_000_000)
/// .into_par_iter()
/// .by_uniform_blocks(1_000_000)
/// .fold(Vec::new, |mut v, e| { v.push(e); v})
/// .reduce(Vec::new, |mut v1, mut v2| { v1.append(&mut v2); v1});
/// assert_eq!(v, (0u32..10_000_000).collect::<Vec<u32>>());
/// ```
#[track_caller]
fn by_uniform_blocks(self, block_size: usize) -> UniformBlocks<Self> {
assert!(block_size != 0, "block_size must not be zero");
UniformBlocks::new(self, block_size)
}
/// Collects the results of the iterator into the specified
/// vector. The vector is always truncated before execution
/// vector. The vector is always cleared before execution
/// begins. If possible, reusing the vector across calls can lead
/// to better performance since it reuses the same backing buffer.
///
@@ -2256,7 +2523,7 @@ pub trait IndexedParallelIterator: ParallelIterator {
/// ```
/// use rayon::prelude::*;
///
/// // any prior data will be truncated
/// // any prior data will be cleared
/// let mut vec = vec![-1, -2, -3];
///
/// (0..5).into_par_iter()
@@ -2269,7 +2536,7 @@ pub trait IndexedParallelIterator: ParallelIterator {
}
/// Unzips the results of the iterator into the specified
/// vectors. The vectors are always truncated before execution
/// vectors. The vectors are always cleared before execution
/// begins. If possible, reusing the vectors across calls can lead
/// to better performance since they reuse the same backing buffer.
///
@@ -2278,7 +2545,7 @@ pub trait IndexedParallelIterator: ParallelIterator {
/// ```
/// use rayon::prelude::*;
///
/// // any prior data will be truncated
/// // any prior data will be cleared
/// let mut left = vec![42; 10];
/// let mut right = vec![-1; 10];
///
@@ -2411,6 +2678,8 @@ pub trait IndexedParallelIterator: ParallelIterator {
/// [`par_chunks()`]: ../slice/trait.ParallelSlice.html#method.par_chunks
/// [`par_chunks_mut()`]: ../slice/trait.ParallelSliceMut.html#method.par_chunks_mut
///
/// **Panics** if `chunk_size` is 0.
///
/// # Examples
///
/// ```
@@ -2419,6 +2688,7 @@ pub trait IndexedParallelIterator: ParallelIterator {
/// let r: Vec<Vec<i32>> = a.into_par_iter().chunks(3).collect();
/// assert_eq!(r, vec![vec![1,2,3], vec![4,5,6], vec![7,8,9], vec![10]]);
/// ```
#[track_caller]
fn chunks(self, chunk_size: usize) -> Chunks<Self> {
assert!(chunk_size != 0, "chunk_size must not be zero");
Chunks::new(self, chunk_size)
@@ -2977,7 +3247,7 @@ pub trait IndexedParallelIterator: ParallelIterator {
/// See the [README] for more details on the internals of parallel
/// iterators.
///
/// [README]: https://github.com/rayon-rs/rayon/blob/master/src/iter/plumbing/README.md
/// [README]: https://github.com/rayon-rs/rayon/blob/main/src/iter/plumbing/README.md
fn drive<C: Consumer<Self::Item>>(self, consumer: C) -> C::Result;
/// Internal method used to define the behavior of this parallel
@@ -2994,7 +3264,7 @@ pub trait IndexedParallelIterator: ParallelIterator {
/// See the [README] for more details on the internals of parallel
/// iterators.
///
/// [README]: https://github.com/rayon-rs/rayon/blob/master/src/iter/plumbing/README.md
/// [README]: https://github.com/rayon-rs/rayon/blob/main/src/iter/plumbing/README.md
fn with_producer<CB: ProducerCallback<Self::Item>>(self, callback: CB) -> CB::Output;
}
@@ -3042,14 +3312,15 @@ where
///
/// If your collection is not naturally parallel, the easiest (and
/// fastest) way to do this is often to collect `par_iter` into a
/// [`LinkedList`] or other intermediate data structure and then
/// sequentially extend your collection. However, a more 'native'
/// technique is to use the [`par_iter.fold`] or
/// [`LinkedList`] (via [`collect_vec_list`]) or another intermediate
/// data structure and then sequentially extend your collection. However,
/// a more 'native' technique is to use the [`par_iter.fold`] or
/// [`par_iter.fold_with`] methods to create the collection.
/// Alternatively, if your collection is 'natively' parallel, you
/// can use `par_iter.for_each` to process each element in turn.
///
/// [`LinkedList`]: https://doc.rust-lang.org/std/collections/struct.LinkedList.html
/// [`collect_vec_list`]: ParallelIterator::collect_vec_list
/// [`par_iter.fold`]: trait.ParallelIterator.html#method.fold
/// [`par_iter.fold_with`]: trait.ParallelIterator.html#method.fold_with
/// [`par_iter.for_each`]: trait.ParallelIterator.html#method.for_each

View File

@@ -1,6 +1,11 @@
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
#[cfg(not(feature = "web_spin_lock"))]
use std::sync::Mutex;
#[cfg(feature = "web_spin_lock")]
use wasm_sync::Mutex;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use crate::iter::plumbing::{bridge_unindexed, Folder, UnindexedConsumer, UnindexedProducer};
use crate::iter::ParallelIterator;
use crate::{current_num_threads, current_thread_index};
@@ -13,6 +18,11 @@ use crate::{current_num_threads, current_thread_index};
/// `par_iter` instead. However, it can still be useful for iterators that are difficult to
/// parallelize by other means, like channels or file or network I/O.
///
/// Iterator items are pulled by `next()` one at a time, synchronized from each thread that is
/// ready for work, so this may become a bottleneck if the serial iterator can't keep up with the
/// parallel demand. The items are not buffered by `IterBridge`, so it's fine to use this with
/// large or even unbounded iterators.
///
/// The resulting iterator is not guaranteed to keep the order of the original iterator.
///
/// # Examples
@@ -99,24 +109,11 @@ impl<Iter: Iterator + Send> UnindexedProducer for &IterParallelProducer<'_, Iter
type Item = Iter::Item;
fn split(self) -> (Self, Option<Self>) {
let mut count = self.split_count.load(Ordering::SeqCst);
loop {
// Check if the iterator is exhausted
if let Some(new_count) = count.checked_sub(1) {
match self.split_count.compare_exchange_weak(
count,
new_count,
Ordering::SeqCst,
Ordering::SeqCst,
) {
Ok(_) => return (self, Some(self)),
Err(last_count) => count = last_count,
}
} else {
return (self, None);
}
}
// Check if the iterator is exhausted
let update = self
.split_count
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |c| c.checked_sub(1));
(self, update.is_ok().then_some(self))
}
fn fold_with<F>(self, mut folder: F) -> F

View File

@@ -35,8 +35,8 @@ modes (which is why there are two):
more like a `for_each` call: each time a new item is produced, the
`consume` method is called with that item. (The traits themselves are
a bit more complex, as they support state that can be threaded
through and ultimately reduced.) Unlike producers, there are two
variants of consumers. The difference is how the split is performed:
through and ultimately reduced.) Like producers, there are two
variants of consumers which differ in how the split is performed:
- in the `Consumer` trait, splitting is done with `split_at`, which
accepts an index where the split should be performed. All
iterators can work in this mode. The resulting halves thus have an

View File

@@ -2,20 +2,19 @@
//! low-level details -- users of parallel iterators should not need to
//! interact with them directly. See [the `plumbing` README][r] for a general overview.
//!
//! [r]: https://github.com/rayon-rs/rayon/blob/master/src/iter/plumbing/README.md
//! [r]: https://github.com/rayon-rs/rayon/blob/main/src/iter/plumbing/README.md
use crate::join_context;
use super::IndexedParallelIterator;
use std::cmp;
use std::usize;
/// The `ProducerCallback` trait is a kind of generic closure,
/// [analogous to `FnOnce`][FnOnce]. See [the corresponding section in
/// the plumbing README][r] for more details.
///
/// [r]: https://github.com/rayon-rs/rayon/blob/master/src/iter/plumbing/README.md#producer-callback
/// [r]: https://github.com/rayon-rs/rayon/blob/main/src/iter/plumbing/README.md#producer-callback
/// [FnOnce]: https://doc.rust-lang.org/std/ops/trait.FnOnce.html
pub trait ProducerCallback<T> {
/// The type of value returned by this callback. Analogous to
@@ -54,7 +53,7 @@ pub trait ProducerCallback<T> {
/// constraints on a required IntoIterator trait, so we inline
/// IntoIterator here until that issue is fixed.
///
/// [r]: https://github.com/rayon-rs/rayon/blob/master/src/iter/plumbing/README.md
/// [r]: https://github.com/rayon-rs/rayon/blob/main/src/iter/plumbing/README.md
/// [20671]: https://github.com/rust-lang/rust/issues/20671
pub trait Producer: Send + Sized {
/// The type of item that will be produced by this producer once
@@ -121,7 +120,7 @@ pub trait Producer: Send + Sized {
/// combine their two results into one. See [the `plumbing`
/// README][r] for further details.
///
/// [r]: https://github.com/rayon-rs/rayon/blob/master/src/iter/plumbing/README.md
/// [r]: https://github.com/rayon-rs/rayon/blob/main/src/iter/plumbing/README.md
/// [fold]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.fold
/// [`Folder`]: trait.Folder.html
/// [`Producer`]: trait.Producer.html
@@ -198,7 +197,7 @@ pub trait Folder<Item>: Sized {
/// used to combine those two results into one. See [the `plumbing`
/// README][r] for further details.
///
/// [r]: https://github.com/rayon-rs/rayon/blob/master/src/iter/plumbing/README.md
/// [r]: https://github.com/rayon-rs/rayon/blob/main/src/iter/plumbing/README.md
pub trait Reducer<Result> {
/// Reduce two final results into one; this is executed after a
/// split.
@@ -275,7 +274,7 @@ impl Splitter {
if stolen {
// This job was stolen! Reset the number of desired splits to the
// thread count, if that's more than we had remaining anyway.
self.splits = cmp::max(crate::current_num_threads(), self.splits / 2);
self.splits = Ord::max(crate::current_num_threads(), self.splits / 2);
true
} else if splits > 0 {
// We have splits remaining, make it so.
@@ -313,14 +312,14 @@ impl LengthSplitter {
fn new(min: usize, max: usize, len: usize) -> LengthSplitter {
let mut splitter = LengthSplitter {
inner: Splitter::new(),
min: cmp::max(min, 1),
min: Ord::max(min, 1),
};
// Divide the given length by the max working length to get the minimum
// number of splits we need to get under that max. This rounds down,
// but the splitter actually gives `next_power_of_two()` pieces anyway.
// e.g. len 12345 / max 100 = 123 min_splits -> 128 pieces.
let min_splits = len / cmp::max(max, 1);
let min_splits = len / Ord::max(max, 1);
// Only update the value if it's not splitting enough already.
if min_splits > splitter.inner.splits {

View File

@@ -13,7 +13,7 @@ where
}
fn mul<T: Product>(left: T, right: T) -> T {
iter::once(left).chain(iter::once(right)).product()
[left, right].into_iter().product()
}
struct ProductConsumer<P: Send> {

View File

@@ -1,7 +1,6 @@
use super::noop::NoopConsumer;
use super::plumbing::*;
use super::*;
use std::cmp::min;
/// `Skip` is an iterator that skips over the first `n` elements.
/// This struct is created by the [`skip()`] method on [`IndexedParallelIterator`]
@@ -21,7 +20,7 @@ where
{
/// Creates a new `Skip` iterator.
pub(super) fn new(base: I, n: usize) -> Self {
let n = min(base.len(), n);
let n = Ord::min(base.len(), n);
Skip { base, n }
}
}

View File

@@ -0,0 +1,144 @@
use super::plumbing::*;
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
/// `SkipAny` is an iterator that skips over `n` elements from anywhere in `I`.
/// This struct is created by the [`skip_any()`] method on [`ParallelIterator`]
///
/// [`skip_any()`]: trait.ParallelIterator.html#method.skip_any
/// [`ParallelIterator`]: trait.ParallelIterator.html
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
#[derive(Clone, Debug)]
pub struct SkipAny<I: ParallelIterator> {
base: I,
count: usize,
}
impl<I> SkipAny<I>
where
I: ParallelIterator,
{
/// Creates a new `SkipAny` iterator.
pub(super) fn new(base: I, count: usize) -> Self {
SkipAny { base, count }
}
}
impl<I> ParallelIterator for SkipAny<I>
where
I: ParallelIterator,
{
type Item = I::Item;
fn drive_unindexed<C>(self, consumer: C) -> C::Result
where
C: UnindexedConsumer<Self::Item>,
{
let consumer1 = SkipAnyConsumer {
base: consumer,
count: &AtomicUsize::new(self.count),
};
self.base.drive_unindexed(consumer1)
}
}
/// ////////////////////////////////////////////////////////////////////////
/// Consumer implementation
struct SkipAnyConsumer<'f, C> {
base: C,
count: &'f AtomicUsize,
}
impl<'f, T, C> Consumer<T> for SkipAnyConsumer<'f, C>
where
C: Consumer<T>,
T: Send,
{
type Folder = SkipAnyFolder<'f, C::Folder>;
type Reducer = C::Reducer;
type Result = C::Result;
fn split_at(self, index: usize) -> (Self, Self, Self::Reducer) {
let (left, right, reducer) = self.base.split_at(index);
(
SkipAnyConsumer { base: left, ..self },
SkipAnyConsumer {
base: right,
..self
},
reducer,
)
}
fn into_folder(self) -> Self::Folder {
SkipAnyFolder {
base: self.base.into_folder(),
count: self.count,
}
}
fn full(&self) -> bool {
self.base.full()
}
}
impl<'f, T, C> UnindexedConsumer<T> for SkipAnyConsumer<'f, C>
where
C: UnindexedConsumer<T>,
T: Send,
{
fn split_off_left(&self) -> Self {
SkipAnyConsumer {
base: self.base.split_off_left(),
..*self
}
}
fn to_reducer(&self) -> Self::Reducer {
self.base.to_reducer()
}
}
struct SkipAnyFolder<'f, C> {
base: C,
count: &'f AtomicUsize,
}
fn checked_decrement(u: &AtomicUsize) -> bool {
u.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |u| u.checked_sub(1))
.is_ok()
}
impl<'f, T, C> Folder<T> for SkipAnyFolder<'f, C>
where
C: Folder<T>,
{
type Result = C::Result;
fn consume(mut self, item: T) -> Self {
if !checked_decrement(self.count) {
self.base = self.base.consume(item);
}
self
}
fn consume_iter<I>(mut self, iter: I) -> Self
where
I: IntoIterator<Item = T>,
{
self.base = self.base.consume_iter(
iter.into_iter()
.skip_while(move |_| checked_decrement(self.count)),
);
self
}
fn complete(self) -> C::Result {
self.base.complete()
}
fn full(&self) -> bool {
self.base.full()
}
}

View File

@@ -0,0 +1,166 @@
use super::plumbing::*;
use super::*;
use std::fmt;
use std::sync::atomic::{AtomicBool, Ordering};
/// `SkipAnyWhile` is an iterator that skips over elements from anywhere in `I`
/// until the callback returns `false`.
/// This struct is created by the [`skip_any_while()`] method on [`ParallelIterator`]
///
/// [`skip_any_while()`]: trait.ParallelIterator.html#method.skip_any_while
/// [`ParallelIterator`]: trait.ParallelIterator.html
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
#[derive(Clone)]
pub struct SkipAnyWhile<I: ParallelIterator, P> {
base: I,
predicate: P,
}
impl<I: ParallelIterator + fmt::Debug, P> fmt::Debug for SkipAnyWhile<I, P> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SkipAnyWhile")
.field("base", &self.base)
.finish()
}
}
impl<I, P> SkipAnyWhile<I, P>
where
I: ParallelIterator,
{
/// Creates a new `SkipAnyWhile` iterator.
pub(super) fn new(base: I, predicate: P) -> Self {
SkipAnyWhile { base, predicate }
}
}
impl<I, P> ParallelIterator for SkipAnyWhile<I, P>
where
I: ParallelIterator,
P: Fn(&I::Item) -> bool + Sync + Send,
{
type Item = I::Item;
fn drive_unindexed<C>(self, consumer: C) -> C::Result
where
C: UnindexedConsumer<Self::Item>,
{
let consumer1 = SkipAnyWhileConsumer {
base: consumer,
predicate: &self.predicate,
skipping: &AtomicBool::new(true),
};
self.base.drive_unindexed(consumer1)
}
}
/// ////////////////////////////////////////////////////////////////////////
/// Consumer implementation
struct SkipAnyWhileConsumer<'p, C, P> {
base: C,
predicate: &'p P,
skipping: &'p AtomicBool,
}
impl<'p, T, C, P> Consumer<T> for SkipAnyWhileConsumer<'p, C, P>
where
C: Consumer<T>,
P: Fn(&T) -> bool + Sync,
{
type Folder = SkipAnyWhileFolder<'p, C::Folder, P>;
type Reducer = C::Reducer;
type Result = C::Result;
fn split_at(self, index: usize) -> (Self, Self, Self::Reducer) {
let (left, right, reducer) = self.base.split_at(index);
(
SkipAnyWhileConsumer { base: left, ..self },
SkipAnyWhileConsumer {
base: right,
..self
},
reducer,
)
}
fn into_folder(self) -> Self::Folder {
SkipAnyWhileFolder {
base: self.base.into_folder(),
predicate: self.predicate,
skipping: self.skipping,
}
}
fn full(&self) -> bool {
self.base.full()
}
}
impl<'p, T, C, P> UnindexedConsumer<T> for SkipAnyWhileConsumer<'p, C, P>
where
C: UnindexedConsumer<T>,
P: Fn(&T) -> bool + Sync,
{
fn split_off_left(&self) -> Self {
SkipAnyWhileConsumer {
base: self.base.split_off_left(),
..*self
}
}
fn to_reducer(&self) -> Self::Reducer {
self.base.to_reducer()
}
}
struct SkipAnyWhileFolder<'p, C, P> {
base: C,
predicate: &'p P,
skipping: &'p AtomicBool,
}
fn skip<T>(item: &T, skipping: &AtomicBool, predicate: &impl Fn(&T) -> bool) -> bool {
if !skipping.load(Ordering::Relaxed) {
return false;
}
if predicate(item) {
return true;
}
skipping.store(false, Ordering::Relaxed);
false
}
impl<'p, T, C, P> Folder<T> for SkipAnyWhileFolder<'p, C, P>
where
C: Folder<T>,
P: Fn(&T) -> bool + 'p,
{
type Result = C::Result;
fn consume(mut self, item: T) -> Self {
if !skip(&item, self.skipping, self.predicate) {
self.base = self.base.consume(item);
}
self
}
fn consume_iter<I>(mut self, iter: I) -> Self
where
I: IntoIterator<Item = T>,
{
self.base = self.base.consume_iter(
iter.into_iter()
.skip_while(move |x| skip(x, self.skipping, self.predicate)),
);
self
}
fn complete(self) -> C::Result {
self.base.complete()
}
fn full(&self) -> bool {
self.base.full()
}
}

View File

@@ -1,5 +1,3 @@
use std::cmp::min;
use super::plumbing::*;
use super::*;
use crate::math::div_round_up;
@@ -116,7 +114,7 @@ where
}
fn split_at(self, index: usize) -> (Self, Self) {
let elem_index = min(index * self.step, self.len);
let elem_index = Ord::min(index * self.step, self.len);
let (left, right) = self.base.split_at(elem_index);
(

View File

@@ -13,7 +13,7 @@ where
}
fn add<T: Sum>(left: T, right: T) -> T {
iter::once(left).chain(iter::once(right)).sum()
[left, right].into_iter().sum()
}
struct SumConsumer<S: Send> {

View File

@@ -1,6 +1,5 @@
use super::plumbing::*;
use super::*;
use std::cmp::min;
/// `Take` is an iterator that iterates over the first `n` elements.
/// This struct is created by the [`take()`] method on [`IndexedParallelIterator`]
@@ -20,7 +19,7 @@ where
{
/// Creates a new `Take` iterator.
pub(super) fn new(base: I, n: usize) -> Self {
let n = min(base.len(), n);
let n = Ord::min(base.len(), n);
Take { base, n }
}
}

View File

@@ -0,0 +1,144 @@
use super::plumbing::*;
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
/// `TakeAny` is an iterator that iterates over `n` elements from anywhere in `I`.
/// This struct is created by the [`take_any()`] method on [`ParallelIterator`]
///
/// [`take_any()`]: trait.ParallelIterator.html#method.take_any
/// [`ParallelIterator`]: trait.ParallelIterator.html
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
#[derive(Clone, Debug)]
pub struct TakeAny<I: ParallelIterator> {
base: I,
count: usize,
}
impl<I> TakeAny<I>
where
I: ParallelIterator,
{
/// Creates a new `TakeAny` iterator.
pub(super) fn new(base: I, count: usize) -> Self {
TakeAny { base, count }
}
}
impl<I> ParallelIterator for TakeAny<I>
where
I: ParallelIterator,
{
type Item = I::Item;
fn drive_unindexed<C>(self, consumer: C) -> C::Result
where
C: UnindexedConsumer<Self::Item>,
{
let consumer1 = TakeAnyConsumer {
base: consumer,
count: &AtomicUsize::new(self.count),
};
self.base.drive_unindexed(consumer1)
}
}
/// ////////////////////////////////////////////////////////////////////////
/// Consumer implementation
struct TakeAnyConsumer<'f, C> {
base: C,
count: &'f AtomicUsize,
}
impl<'f, T, C> Consumer<T> for TakeAnyConsumer<'f, C>
where
C: Consumer<T>,
T: Send,
{
type Folder = TakeAnyFolder<'f, C::Folder>;
type Reducer = C::Reducer;
type Result = C::Result;
fn split_at(self, index: usize) -> (Self, Self, Self::Reducer) {
let (left, right, reducer) = self.base.split_at(index);
(
TakeAnyConsumer { base: left, ..self },
TakeAnyConsumer {
base: right,
..self
},
reducer,
)
}
fn into_folder(self) -> Self::Folder {
TakeAnyFolder {
base: self.base.into_folder(),
count: self.count,
}
}
fn full(&self) -> bool {
self.count.load(Ordering::Relaxed) == 0 || self.base.full()
}
}
impl<'f, T, C> UnindexedConsumer<T> for TakeAnyConsumer<'f, C>
where
C: UnindexedConsumer<T>,
T: Send,
{
fn split_off_left(&self) -> Self {
TakeAnyConsumer {
base: self.base.split_off_left(),
..*self
}
}
fn to_reducer(&self) -> Self::Reducer {
self.base.to_reducer()
}
}
struct TakeAnyFolder<'f, C> {
base: C,
count: &'f AtomicUsize,
}
fn checked_decrement(u: &AtomicUsize) -> bool {
u.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |u| u.checked_sub(1))
.is_ok()
}
impl<'f, T, C> Folder<T> for TakeAnyFolder<'f, C>
where
C: Folder<T>,
{
type Result = C::Result;
fn consume(mut self, item: T) -> Self {
if checked_decrement(self.count) {
self.base = self.base.consume(item);
}
self
}
fn consume_iter<I>(mut self, iter: I) -> Self
where
I: IntoIterator<Item = T>,
{
self.base = self.base.consume_iter(
iter.into_iter()
.take_while(move |_| checked_decrement(self.count)),
);
self
}
fn complete(self) -> C::Result {
self.base.complete()
}
fn full(&self) -> bool {
self.count.load(Ordering::Relaxed) == 0 || self.base.full()
}
}

View File

@@ -0,0 +1,166 @@
use super::plumbing::*;
use super::*;
use std::fmt;
use std::sync::atomic::{AtomicBool, Ordering};
/// `TakeAnyWhile` is an iterator that iterates over elements from anywhere in `I`
/// until the callback returns `false`.
/// This struct is created by the [`take_any_while()`] method on [`ParallelIterator`]
///
/// [`take_any_while()`]: trait.ParallelIterator.html#method.take_any_while
/// [`ParallelIterator`]: trait.ParallelIterator.html
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
#[derive(Clone)]
pub struct TakeAnyWhile<I: ParallelIterator, P> {
base: I,
predicate: P,
}
impl<I: ParallelIterator + fmt::Debug, P> fmt::Debug for TakeAnyWhile<I, P> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TakeAnyWhile")
.field("base", &self.base)
.finish()
}
}
impl<I, P> TakeAnyWhile<I, P>
where
I: ParallelIterator,
{
/// Creates a new `TakeAnyWhile` iterator.
pub(super) fn new(base: I, predicate: P) -> Self {
TakeAnyWhile { base, predicate }
}
}
impl<I, P> ParallelIterator for TakeAnyWhile<I, P>
where
I: ParallelIterator,
P: Fn(&I::Item) -> bool + Sync + Send,
{
type Item = I::Item;
fn drive_unindexed<C>(self, consumer: C) -> C::Result
where
C: UnindexedConsumer<Self::Item>,
{
let consumer1 = TakeAnyWhileConsumer {
base: consumer,
predicate: &self.predicate,
taking: &AtomicBool::new(true),
};
self.base.drive_unindexed(consumer1)
}
}
/// ////////////////////////////////////////////////////////////////////////
/// Consumer implementation
struct TakeAnyWhileConsumer<'p, C, P> {
base: C,
predicate: &'p P,
taking: &'p AtomicBool,
}
impl<'p, T, C, P> Consumer<T> for TakeAnyWhileConsumer<'p, C, P>
where
C: Consumer<T>,
P: Fn(&T) -> bool + Sync,
{
type Folder = TakeAnyWhileFolder<'p, C::Folder, P>;
type Reducer = C::Reducer;
type Result = C::Result;
fn split_at(self, index: usize) -> (Self, Self, Self::Reducer) {
let (left, right, reducer) = self.base.split_at(index);
(
TakeAnyWhileConsumer { base: left, ..self },
TakeAnyWhileConsumer {
base: right,
..self
},
reducer,
)
}
fn into_folder(self) -> Self::Folder {
TakeAnyWhileFolder {
base: self.base.into_folder(),
predicate: self.predicate,
taking: self.taking,
}
}
fn full(&self) -> bool {
!self.taking.load(Ordering::Relaxed) || self.base.full()
}
}
impl<'p, T, C, P> UnindexedConsumer<T> for TakeAnyWhileConsumer<'p, C, P>
where
C: UnindexedConsumer<T>,
P: Fn(&T) -> bool + Sync,
{
fn split_off_left(&self) -> Self {
TakeAnyWhileConsumer {
base: self.base.split_off_left(),
..*self
}
}
fn to_reducer(&self) -> Self::Reducer {
self.base.to_reducer()
}
}
struct TakeAnyWhileFolder<'p, C, P> {
base: C,
predicate: &'p P,
taking: &'p AtomicBool,
}
fn take<T>(item: &T, taking: &AtomicBool, predicate: &impl Fn(&T) -> bool) -> bool {
if !taking.load(Ordering::Relaxed) {
return false;
}
if predicate(item) {
return true;
}
taking.store(false, Ordering::Relaxed);
false
}
impl<'p, T, C, P> Folder<T> for TakeAnyWhileFolder<'p, C, P>
where
C: Folder<T>,
P: Fn(&T) -> bool + 'p,
{
type Result = C::Result;
fn consume(mut self, item: T) -> Self {
if take(&item, self.taking, self.predicate) {
self.base = self.base.consume(item);
}
self
}
fn consume_iter<I>(mut self, iter: I) -> Self
where
I: IntoIterator<Item = T>,
{
self.base = self.base.consume_iter(
iter.into_iter()
.take_while(move |x| take(x, self.taking, self.predicate)),
);
self
}
fn complete(self) -> C::Result {
self.base.complete()
}
fn full(&self) -> bool {
!self.taking.load(Ordering::Relaxed) || self.base.full()
}
}

View File

@@ -7,10 +7,10 @@ use rayon_core::*;
use rand::distributions::Standard;
use rand::{Rng, SeedableRng};
use rand_xorshift::XorShiftRng;
use std::collections::LinkedList;
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::collections::{BinaryHeap, VecDeque};
use std::f64;
use std::ffi::OsStr;
use std::fmt::Debug;
use std::sync::mpsc;
use std::usize;
@@ -60,8 +60,7 @@ fn execute_unindexed_range() {
#[test]
fn execute_pseudo_indexed_range() {
use std::i128::MAX;
let range = MAX - 1024..MAX;
let range = i128::MAX - 1024..i128::MAX;
// Given `Some` length, collecting `Vec` will try to act indexed.
let a = range.clone().into_par_iter();
@@ -278,6 +277,7 @@ fn check_skip() {
let mut v1 = Vec::new();
a.par_iter().skip(0).collect_into_vec(&mut v1);
#[allow(clippy::iter_skip_zero)]
let v2 = a.iter().skip(0).collect::<Vec<_>>();
assert_eq!(v1, v2);
@@ -468,6 +468,7 @@ fn check_cmp_gt_to_seq() {
}
#[test]
#[cfg_attr(any(target_os = "emscripten", target_family = "wasm"), ignore)]
fn check_cmp_short_circuit() {
// We only use a single thread in order to make the short-circuit behavior deterministic.
let pool = ThreadPoolBuilder::new().num_threads(1).build().unwrap();
@@ -497,6 +498,7 @@ fn check_cmp_short_circuit() {
}
#[test]
#[cfg_attr(any(target_os = "emscripten", target_family = "wasm"), ignore)]
fn check_partial_cmp_short_circuit() {
// We only use a single thread to make the short-circuit behavior deterministic.
let pool = ThreadPoolBuilder::new().num_threads(1).build().unwrap();
@@ -526,6 +528,7 @@ fn check_partial_cmp_short_circuit() {
}
#[test]
#[cfg_attr(any(target_os = "emscripten", target_family = "wasm"), ignore)]
fn check_partial_cmp_nan_short_circuit() {
// We only use a single thread to make the short-circuit behavior deterministic.
let pool = ThreadPoolBuilder::new().num_threads(1).build().unwrap();
@@ -626,7 +629,7 @@ fn check_partial_cmp_none_direct() {
let result = a.par_iter().partial_cmp(b.par_iter());
assert!(result == None);
assert!(result.is_none());
}
#[test]
@@ -651,7 +654,7 @@ fn check_partial_cmp_late_nan_direct() {
}
#[test]
fn check_partial_cmp_late_nane_to_seq() {
fn check_partial_cmp_late_nan_to_seq() {
let a = vec![0.0, f64::NAN];
let b = vec![1.0, 1.0];
@@ -980,6 +983,25 @@ fn check_slice_split() {
assert_eq!(v, &[&slice[..1], &slice[..0], &slice[3..]]);
}
#[test]
fn check_slice_split_inclusive() {
let v: Vec<_> = (0..1000).collect();
for m in 1..100 {
let a: Vec<_> = v.split_inclusive(|x| x % m == 0).collect();
let b: Vec<_> = v.par_split_inclusive(|x| x % m == 0).collect();
assert_eq!(a, b);
}
// same as std::slice::split_inclusive() examples
let slice = [10, 40, 33, 20];
let v: Vec<_> = slice.par_split_inclusive(|num| num % 3 == 0).collect();
assert_eq!(v, &[&slice[..3], &slice[3..]]);
let slice = [3, 10, 40, 33];
let v: Vec<_> = slice.par_split_inclusive(|num| num % 3 == 0).collect();
assert_eq!(v, &[&slice[..1], &slice[1..]]);
}
#[test]
fn check_slice_split_mut() {
let mut v1: Vec<_> = (0..1000).collect();
@@ -998,6 +1020,26 @@ fn check_slice_split_mut() {
assert_eq!(v, [1, 40, 30, 1, 60, 1]);
}
#[test]
fn check_slice_split_inclusive_mut() {
let mut v1: Vec<_> = (0..1000).collect();
let mut v2 = v1.clone();
for m in 1..100 {
let a: Vec<_> = v1.split_inclusive_mut(|x| x % m == 0).collect();
let b: Vec<_> = v2.par_split_inclusive_mut(|x| x % m == 0).collect();
assert_eq!(a, b);
}
// same as std::slice::split_inclusive_mut() example
let mut v = [10, 40, 30, 20, 60, 50];
v.par_split_inclusive_mut(|num| num % 3 == 0)
.for_each(|group| {
let terminator_idx = group.len() - 1;
group[terminator_idx] = 1;
});
assert_eq!(v, [10, 40, 1, 20, 1, 1]);
}
#[test]
fn check_chunks() {
let a: Vec<i32> = vec![1, 5, 10, 4, 100, 3, 1000, 2, 10000, 1];
@@ -1526,13 +1568,27 @@ fn par_iter_collect_cows() {
assert_eq!(a, b);
// Collects `str` into a `String`
let a: Cow<'_, str> = s.split_whitespace().collect();
let b: Cow<'_, str> = s.par_split_whitespace().collect();
let sw = s.split_whitespace();
let psw = s.par_split_whitespace();
let a: Cow<'_, str> = sw.clone().collect();
let b: Cow<'_, str> = psw.clone().collect();
assert_eq!(a, b);
// Collects `String` into a `String`
let a: Cow<'_, str> = s.split_whitespace().map(str::to_owned).collect();
let b: Cow<'_, str> = s.par_split_whitespace().map(str::to_owned).collect();
let a: Cow<'_, str> = sw.map(str::to_owned).collect();
let b: Cow<'_, str> = psw.map(str::to_owned).collect();
assert_eq!(a, b);
// Collects `OsStr` into a `OsString`
let sw = s.split_whitespace().map(OsStr::new);
let psw = s.par_split_whitespace().map(OsStr::new);
let a: Cow<'_, OsStr> = Cow::Owned(sw.clone().collect());
let b: Cow<'_, OsStr> = psw.clone().collect();
assert_eq!(a, b);
// Collects `OsString` into a `OsString`
let a: Cow<'_, OsStr> = Cow::Owned(sw.map(OsStr::to_owned).collect());
let b: Cow<'_, OsStr> = psw.map(OsStr::to_owned).collect();
assert_eq!(a, b);
}
@@ -1651,8 +1707,8 @@ fn check_lengths() {
let range = 0..1024 * 1024;
// Check against normalized values.
let min_check = cmp::min(cmp::max(min, 1), range.len());
let max_check = cmp::max(max, min_check.saturating_add(min_check - 1));
let min_check = Ord::min(Ord::max(min, 1), range.len());
let max_check = Ord::max(max, min_check.saturating_add(min_check - 1));
assert!(
range
@@ -2183,3 +2239,95 @@ fn check_update() {
assert_eq!(v, vec![vec![1, 0], vec![3, 2, 1, 0]]);
}
#[test]
fn walk_tree_prefix() {
let v: Vec<u32> = crate::iter::walk_tree_prefix(0u32..100, |r| {
// root is smallest
let mid = (r.start + 1 + r.end) / 2;
// small indices to the left, large to the right
std::iter::once((r.start + 1)..mid)
.chain(std::iter::once(mid..r.end))
.filter(|r| !r.is_empty())
})
.map(|r| r.start)
.collect();
assert!(v.into_iter().eq(0..100));
}
#[test]
fn walk_tree_postfix() {
let v: Vec<_> = crate::iter::walk_tree_postfix(0u64..100, |r| {
// root is largest
let mid = (r.start + r.end - 1) / 2;
// small indices to the left, large to the right
std::iter::once(r.start..mid)
.chain(std::iter::once(mid..(r.end - 1)))
.filter(|r| !r.is_empty())
})
.map(|r| r.end - 1)
.collect();
assert!(v.into_iter().eq(0..100));
}
#[test]
fn walk_flat_tree_prefix() {
let v: Vec<_> =
crate::iter::walk_tree_prefix(0, |&e| if e < 99 { Some(e + 1) } else { None }).collect();
assert!(v.into_iter().eq(0..100));
}
#[test]
fn walk_flat_tree_postfix() {
let v: Vec<_> =
crate::iter::walk_tree_postfix(99, |&e| if e > 0 { Some(e - 1) } else { None }).collect();
assert!(v.into_iter().eq(0..100));
}
#[test]
fn walk_tree_prefix_degree5() {
let depth = 5;
let nodes_number = (1 - 5i32.pow(depth)) / (1 - 5);
let nodes = (0..nodes_number).collect::<Vec<_>>();
let v: Vec<i32> = crate::iter::walk_tree_prefix(nodes.as_slice(), |&r| {
r.split_first()
.into_iter()
.filter_map(|(_, r)| if r.is_empty() { None } else { Some(r) })
.flat_map(|r| r.chunks(r.len() / 5))
})
.filter_map(|r| r.first().copied())
.collect();
assert_eq!(v, nodes);
}
#[test]
fn walk_tree_postfix_degree5() {
let depth = 5;
let nodes_number = (1 - 5i32.pow(depth)) / (1 - 5);
let nodes = (0..nodes_number).collect::<Vec<_>>();
let v: Vec<i32> = crate::iter::walk_tree_postfix(nodes.as_slice(), |&r| {
r.split_last()
.into_iter()
.filter_map(|(_, r)| if r.is_empty() { None } else { Some(r) })
.flat_map(|r| r.chunks(r.len() / 5))
})
.filter_map(|r| r.last().copied())
.collect();
assert_eq!(v, nodes)
}
#[test]
fn blocks() {
let count = AtomicUsize::new(0);
let v: Vec<usize> = (0..1000)
.into_par_iter()
.map(|_| count.fetch_add(1, Ordering::Relaxed))
.by_uniform_blocks(100)
.collect();
let m = v
.chunks(100)
.map(|c| c.iter().max().copied().unwrap())
.collect::<Vec<usize>>();
assert!(m.windows(2).all(|w| w[0].lt(&w[1])));
assert_eq!(v.len(), 1000);
}

View File

@@ -0,0 +1,529 @@
use crate::iter::plumbing::{bridge_unindexed, Folder, UnindexedConsumer, UnindexedProducer};
use crate::prelude::*;
use std::iter::once;
#[derive(Debug)]
struct WalkTreePrefixProducer<'b, S, B> {
to_explore: Vec<S>, // nodes (and subtrees) we have to process
seen: Vec<S>, // nodes which have already been explored
children_of: &'b B, // function generating children
}
impl<S, B, I> UnindexedProducer for WalkTreePrefixProducer<'_, S, B>
where
S: Send,
B: Fn(&S) -> I + Send + Sync,
I: IntoIterator<Item = S>,
I::IntoIter: DoubleEndedIterator,
{
type Item = S;
fn split(mut self) -> (Self, Option<Self>) {
// explore while front is of size one.
while self.to_explore.len() == 1 {
let front_node = self.to_explore.pop().unwrap();
self.to_explore
.extend((self.children_of)(&front_node).into_iter().rev());
self.seen.push(front_node);
}
// now take half of the front.
let right_children = split_vec(&mut self.to_explore);
let right = right_children
.map(|mut c| {
std::mem::swap(&mut c, &mut self.to_explore);
WalkTreePrefixProducer {
to_explore: c,
seen: Vec::new(),
children_of: self.children_of,
}
})
.or_else(|| {
// we can still try to divide 'seen'
let right_seen = split_vec(&mut self.seen);
right_seen.map(|s| WalkTreePrefixProducer {
to_explore: Default::default(),
seen: s,
children_of: self.children_of,
})
});
(self, right)
}
fn fold_with<F>(mut self, mut folder: F) -> F
where
F: Folder<Self::Item>,
{
// start by consuming everything seen
folder = folder.consume_iter(self.seen);
if folder.full() {
return folder;
}
// now do all remaining explorations
while let Some(e) = self.to_explore.pop() {
self.to_explore
.extend((self.children_of)(&e).into_iter().rev());
folder = folder.consume(e);
if folder.full() {
return folder;
}
}
folder
}
}
/// ParallelIterator for arbitrary tree-shaped patterns.
/// Returned by the [`walk_tree_prefix()`] function.
#[derive(Debug)]
pub struct WalkTreePrefix<S, B> {
initial_state: S,
children_of: B,
}
impl<S, B, I> ParallelIterator for WalkTreePrefix<S, B>
where
S: Send,
B: Fn(&S) -> I + Send + Sync,
I: IntoIterator<Item = S>,
I::IntoIter: DoubleEndedIterator,
{
type Item = S;
fn drive_unindexed<C>(self, consumer: C) -> C::Result
where
C: UnindexedConsumer<Self::Item>,
{
let producer = WalkTreePrefixProducer {
to_explore: once(self.initial_state).collect(),
seen: Vec::new(),
children_of: &self.children_of,
};
bridge_unindexed(producer, consumer)
}
}
/// Create a tree-like prefix parallel iterator from an initial root node.
/// The `children_of` function should take a node and return an iterator over its child nodes.
/// The best parallelization is obtained when the tree is balanced
/// but we should also be able to handle harder cases.
///
/// # Ordering
///
/// This function guarantees a prefix ordering. See also [`walk_tree_postfix`],
/// which guarantees a postfix order.
/// If you don't care about ordering, you should use [`walk_tree`],
/// which will use whatever is believed to be fastest.
/// For example a perfect binary tree of 7 nodes will reduced in the following order:
///
/// ```text
/// a
/// / \
/// / \
/// b c
/// / \ / \
/// d e f g
///
/// reduced as a,b,d,e,c,f,g
///
/// ```
///
/// # Example
///
/// ```text
/// 4
/// / \
/// / \
/// 2 3
/// / \
/// 1 2
/// ```
///
/// ```
/// use rayon::iter::walk_tree_prefix;
/// use rayon::prelude::*;
///
/// let par_iter = walk_tree_prefix(4, |&e| {
/// if e <= 2 {
/// Vec::new()
/// } else {
/// vec![e / 2, e / 2 + 1]
/// }
/// });
/// assert_eq!(par_iter.sum::<u32>(), 12);
/// ```
///
/// # Example
///
/// ```
/// use rayon::prelude::*;
/// use rayon::iter::walk_tree_prefix;
///
/// struct Node {
/// content: u32,
/// left: Option<Box<Node>>,
/// right: Option<Box<Node>>,
/// }
///
/// // Here we loop on the following tree:
/// //
/// // 10
/// // / \
/// // / \
/// // 3 14
/// // \
/// // \
/// // 18
///
/// let root = Node {
/// content: 10,
/// left: Some(Box::new(Node {
/// content: 3,
/// left: None,
/// right: None,
/// })),
/// right: Some(Box::new(Node {
/// content: 14,
/// left: None,
/// right: Some(Box::new(Node {
/// content: 18,
/// left: None,
/// right: None,
/// })),
/// })),
/// };
///
/// let mut v: Vec<u32> = walk_tree_prefix(&root, |r| {
/// r.left
/// .as_ref()
/// .into_iter()
/// .chain(r.right.as_ref())
/// .map(|n| &**n)
/// })
/// .map(|node| node.content)
/// .collect();
/// assert_eq!(v, vec![10, 3, 14, 18]);
/// ```
///
pub fn walk_tree_prefix<S, B, I>(root: S, children_of: B) -> WalkTreePrefix<S, B>
where
S: Send,
B: Fn(&S) -> I + Send + Sync,
I: IntoIterator<Item = S>,
I::IntoIter: DoubleEndedIterator,
{
WalkTreePrefix {
initial_state: root,
children_of,
}
}
// post fix
#[derive(Debug)]
struct WalkTreePostfixProducer<'b, S, B> {
to_explore: Vec<S>, // nodes (and subtrees) we have to process
seen: Vec<S>, // nodes which have already been explored
children_of: &'b B, // function generating children
}
impl<S, B, I> UnindexedProducer for WalkTreePostfixProducer<'_, S, B>
where
S: Send,
B: Fn(&S) -> I + Send + Sync,
I: IntoIterator<Item = S>,
{
type Item = S;
fn split(mut self) -> (Self, Option<Self>) {
// explore while front is of size one.
while self.to_explore.len() == 1 {
let front_node = self.to_explore.pop().unwrap();
self.to_explore
.extend((self.children_of)(&front_node).into_iter());
self.seen.push(front_node);
}
// now take half of the front.
let right_children = split_vec(&mut self.to_explore);
let right = right_children
.map(|c| {
let right_seen = std::mem::take(&mut self.seen); // postfix -> upper nodes are processed last
WalkTreePostfixProducer {
to_explore: c,
seen: right_seen,
children_of: self.children_of,
}
})
.or_else(|| {
// we can still try to divide 'seen'
let right_seen = split_vec(&mut self.seen);
right_seen.map(|mut s| {
std::mem::swap(&mut self.seen, &mut s);
WalkTreePostfixProducer {
to_explore: Default::default(),
seen: s,
children_of: self.children_of,
}
})
});
(self, right)
}
fn fold_with<F>(self, mut folder: F) -> F
where
F: Folder<Self::Item>,
{
// now do all remaining explorations
for e in self.to_explore {
folder = consume_rec_postfix(&self.children_of, e, folder);
if folder.full() {
return folder;
}
}
// end by consuming everything seen
folder.consume_iter(self.seen.into_iter().rev())
}
}
fn consume_rec_postfix<F, S, B, I>(children_of: &B, s: S, mut folder: F) -> F
where
F: Folder<S>,
B: Fn(&S) -> I,
I: IntoIterator<Item = S>,
{
let children = (children_of)(&s).into_iter();
for child in children {
folder = consume_rec_postfix(children_of, child, folder);
if folder.full() {
return folder;
}
}
folder.consume(s)
}
/// ParallelIterator for arbitrary tree-shaped patterns.
/// Returned by the [`walk_tree_postfix()`] function.
#[derive(Debug)]
pub struct WalkTreePostfix<S, B> {
initial_state: S,
children_of: B,
}
impl<S, B, I> ParallelIterator for WalkTreePostfix<S, B>
where
S: Send,
B: Fn(&S) -> I + Send + Sync,
I: IntoIterator<Item = S>,
{
type Item = S;
fn drive_unindexed<C>(self, consumer: C) -> C::Result
where
C: UnindexedConsumer<Self::Item>,
{
let producer = WalkTreePostfixProducer {
to_explore: once(self.initial_state).collect(),
seen: Vec::new(),
children_of: &self.children_of,
};
bridge_unindexed(producer, consumer)
}
}
/// Divide given vector in two equally sized vectors.
/// Return `None` if initial size is <=1.
/// We return the first half and keep the last half in `v`.
fn split_vec<T>(v: &mut Vec<T>) -> Option<Vec<T>> {
if v.len() <= 1 {
None
} else {
let n = v.len() / 2;
Some(v.split_off(n))
}
}
/// Create a tree like postfix parallel iterator from an initial root node.
/// The `children_of` function should take a node and iterate on all of its child nodes.
/// The best parallelization is obtained when the tree is balanced
/// but we should also be able to handle harder cases.
///
/// # Ordering
///
/// This function guarantees a postfix ordering. See also [`walk_tree_prefix`] which guarantees a
/// prefix order. If you don't care about ordering, you should use [`walk_tree`], which will use
/// whatever is believed to be fastest.
///
/// Between siblings, children are reduced in order -- that is first children are reduced first.
///
/// For example a perfect binary tree of 7 nodes will reduced in the following order:
///
/// ```text
/// a
/// / \
/// / \
/// b c
/// / \ / \
/// d e f g
///
/// reduced as d,e,b,f,g,c,a
///
/// ```
///
/// # Example
///
/// ```text
/// 4
/// / \
/// / \
/// 2 3
/// / \
/// 1 2
/// ```
///
/// ```
/// use rayon::iter::walk_tree_postfix;
/// use rayon::prelude::*;
///
/// let par_iter = walk_tree_postfix(4, |&e| {
/// if e <= 2 {
/// Vec::new()
/// } else {
/// vec![e / 2, e / 2 + 1]
/// }
/// });
/// assert_eq!(par_iter.sum::<u32>(), 12);
/// ```
///
/// # Example
///
/// ```
/// use rayon::prelude::*;
/// use rayon::iter::walk_tree_postfix;
///
/// struct Node {
/// content: u32,
/// left: Option<Box<Node>>,
/// right: Option<Box<Node>>,
/// }
///
/// // Here we loop on the following tree:
/// //
/// // 10
/// // / \
/// // / \
/// // 3 14
/// // \
/// // \
/// // 18
///
/// let root = Node {
/// content: 10,
/// left: Some(Box::new(Node {
/// content: 3,
/// left: None,
/// right: None,
/// })),
/// right: Some(Box::new(Node {
/// content: 14,
/// left: None,
/// right: Some(Box::new(Node {
/// content: 18,
/// left: None,
/// right: None,
/// })),
/// })),
/// };
///
/// let mut v: Vec<u32> = walk_tree_postfix(&root, |r| {
/// r.left
/// .as_ref()
/// .into_iter()
/// .chain(r.right.as_ref())
/// .map(|n| &**n)
/// })
/// .map(|node| node.content)
/// .collect();
/// assert_eq!(v, vec![3, 18, 14, 10]);
/// ```
///
pub fn walk_tree_postfix<S, B, I>(root: S, children_of: B) -> WalkTreePostfix<S, B>
where
S: Send,
B: Fn(&S) -> I + Send + Sync,
I: IntoIterator<Item = S>,
{
WalkTreePostfix {
initial_state: root,
children_of,
}
}
/// ParallelIterator for arbitrary tree-shaped patterns.
/// Returned by the [`walk_tree()`] function.
#[derive(Debug)]
pub struct WalkTree<S, B>(WalkTreePostfix<S, B>);
/// Create a tree like parallel iterator from an initial root node.
/// The `children_of` function should take a node and iterate on all of its child nodes.
/// The best parallelization is obtained when the tree is balanced
/// but we should also be able to handle harder cases.
///
/// # Ordering
///
/// This function does not guarantee any ordering but will
/// use whatever algorithm is thought to achieve the fastest traversal.
/// See also [`walk_tree_prefix`] which guarantees a
/// prefix order and [`walk_tree_postfix`] which guarantees a postfix order.
///
/// # Example
///
/// ```text
/// 4
/// / \
/// / \
/// 2 3
/// / \
/// 1 2
/// ```
///
/// ```
/// use rayon::iter::walk_tree;
/// use rayon::prelude::*;
///
/// let par_iter = walk_tree(4, |&e| {
/// if e <= 2 {
/// Vec::new()
/// } else {
/// vec![e / 2, e / 2 + 1]
/// }
/// });
/// assert_eq!(par_iter.sum::<u32>(), 12);
/// ```
pub fn walk_tree<S, B, I>(root: S, children_of: B) -> WalkTree<S, B>
where
S: Send,
B: Fn(&S) -> I + Send + Sync,
I: IntoIterator<Item = S>,
I::IntoIter: DoubleEndedIterator,
{
let walker = WalkTreePostfix {
initial_state: root,
children_of,
};
WalkTree(walker)
}
impl<S, B, I> ParallelIterator for WalkTree<S, B>
where
S: Send,
B: Fn(&S) -> I + Send + Sync,
I: IntoIterator<Item = S> + Send,
I::IntoIter: DoubleEndedIterator,
{
type Item = S;
fn drive_unindexed<C>(self, consumer: C) -> C::Result
where
C: UnindexedConsumer<Self::Item>,
{
self.0.drive_unindexed(consumer)
}
}

View File

@@ -1,6 +1,5 @@
use super::plumbing::*;
use super::*;
use std::cmp;
use std::iter;
/// `Zip` is an iterator that zips up `a` and `b` into a single iterator
@@ -59,7 +58,7 @@ where
}
fn len(&self) -> usize {
cmp::min(self.a.len(), self.b.len())
Ord::min(self.a.len(), self.b.len())
}
fn with_producer<CB>(self, callback: CB) -> CB::Output
@@ -135,11 +134,11 @@ impl<A: Producer, B: Producer> Producer for ZipProducer<A, B> {
}
fn min_len(&self) -> usize {
cmp::max(self.a.min_len(), self.b.min_len())
Ord::max(self.a.min_len(), self.b.min_len())
}
fn max_len(&self) -> usize {
cmp::min(self.a.max_len(), self.b.max_len())
Ord::min(self.a.max_len(), self.b.max_len())
}
fn split_at(self, index: usize) -> (Self, Self) {

View File

@@ -3,10 +3,10 @@
#![deny(unreachable_pub)]
#![warn(rust_2018_idioms)]
//! Data-parallelism library that makes it easy to convert sequential
//! computations into parallel
//! Rayon is a data-parallelism library that makes it easy to convert sequential
//! computations into parallel.
//!
//! Rayon is lightweight and convenient for introducing parallelism into existing
//! It is lightweight and convenient for introducing parallelism into existing
//! code. It guarantees data-race free executions and takes advantage of
//! parallelism when sensible, based on work-load at runtime.
//!
@@ -76,11 +76,16 @@
//! [the `collections` from `std`]: https://doc.rust-lang.org/std/collections/index.html
//! [`std`]: https://doc.rust-lang.org/std/
//!
//! # Targets without threading
//!
//! Rayon has limited support for targets without `std` threading implementations.
//! See the [`rayon_core`] documentation for more information about its global fallback.
//!
//! # Other questions?
//!
//! See [the Rayon FAQ][faq].
//!
//! [faq]: https://github.com/rayon-rs/rayon/blob/master/FAQ.md
//! [faq]: https://github.com/rayon-rs/rayon/blob/main/FAQ.md
#[macro_use]
mod delegate;
@@ -119,6 +124,7 @@ pub use rayon_core::{in_place_scope, scope, Scope};
pub use rayon_core::{in_place_scope_fifo, scope_fifo, ScopeFifo};
pub use rayon_core::{join, join_context};
pub use rayon_core::{spawn, spawn_fifo};
pub use rayon_core::{yield_local, yield_now, Yield};
/// We need to transmit raw pointers across threads. It is possible to do this
/// without any unsafe code by converting pointers to usize or to AtomicPtr<T>
@@ -146,7 +152,7 @@ impl<T> SendPtr<T> {
// Implement Clone without the T: Clone bound from the derive
impl<T> Clone for SendPtr<T> {
fn clone(&self) -> Self {
Self(self.0)
*self
}
}

View File

@@ -18,10 +18,7 @@
use crate::iter::plumbing::*;
use crate::iter::*;
use std::char;
use std::convert::TryFrom;
use std::ops::Range;
use std::usize;
/// Parallel iterator over a range, implemented for all integer types and `char`.
///
@@ -379,8 +376,6 @@ fn check_range_split_at_overflow() {
#[test]
fn test_i128_len_doesnt_overflow() {
use std::{i128, u128};
// Using parse because some versions of rust don't allow long literals
let octillion: i128 = "1000000000000000000000000000".parse().unwrap();
let producer = IterProducer {
@@ -396,7 +391,6 @@ fn test_i128_len_doesnt_overflow() {
#[test]
fn test_u64_opt_len() {
use std::{u64, usize};
assert_eq!(Some(100), (0..100u64).into_par_iter().opt_len());
assert_eq!(
Some(usize::MAX),
@@ -415,7 +409,6 @@ fn test_u64_opt_len() {
#[test]
fn test_u128_opt_len() {
use std::{u128, usize};
assert_eq!(Some(100), (0..100u128).into_par_iter().opt_len());
assert_eq!(
Some(usize::MAX),
@@ -431,7 +424,6 @@ fn test_u128_opt_len() {
#[cfg(target_pointer_width = "64")]
fn test_usize_i64_overflow() {
use crate::ThreadPoolBuilder;
use std::i64;
let iter = (-2..i64::MAX).into_par_iter();
assert_eq!(iter.opt_len(), Some(i64::MAX as usize + 2));

View File

@@ -18,7 +18,6 @@
use crate::iter::plumbing::*;
use crate::iter::*;
use std::char;
use std::ops::RangeInclusive;
/// Parallel iterator over an inclusive range, implemented for all integer types and `char`.
@@ -313,7 +312,6 @@ impl IndexedParallelIterator for Iter<char> {
#[test]
#[cfg(target_pointer_width = "64")]
fn test_u32_opt_len() {
use std::u32;
assert_eq!(Some(101), (0..=100u32).into_par_iter().opt_len());
assert_eq!(
Some(u32::MAX as usize),
@@ -327,7 +325,6 @@ fn test_u32_opt_len() {
#[test]
fn test_u64_opt_len() {
use std::{u64, usize};
assert_eq!(Some(101), (0..=100u64).into_par_iter().opt_len());
assert_eq!(
Some(usize::MAX),
@@ -339,7 +336,6 @@ fn test_u64_opt_len() {
#[test]
fn test_u128_opt_len() {
use std::{u128, usize};
assert_eq!(Some(101), (0..=100u128).into_par_iter().opt_len());
assert_eq!(
Some(usize::MAX),
@@ -355,7 +351,6 @@ fn test_u128_opt_len() {
#[cfg(target_pointer_width = "64")]
fn test_usize_i64_overflow() {
use crate::ThreadPoolBuilder;
use std::i64;
let iter = (-2..=i64::MAX).into_par_iter();
assert_eq!(iter.opt_len(), Some(i64::MAX as usize + 3));

View File

@@ -0,0 +1,244 @@
use crate::iter::plumbing::*;
use crate::iter::*;
use std::marker::PhantomData;
use std::{fmt, mem};
trait ChunkBySlice<T>: AsRef<[T]> + Default + Send {
fn split(self, index: usize) -> (Self, Self);
fn find(&self, pred: &impl Fn(&T, &T) -> bool, start: usize, end: usize) -> Option<usize> {
self.as_ref()[start..end]
.windows(2)
.position(move |w| !pred(&w[0], &w[1]))
.map(|i| i + 1)
}
fn rfind(&self, pred: &impl Fn(&T, &T) -> bool, end: usize) -> Option<usize> {
self.as_ref()[..end]
.windows(2)
.rposition(move |w| !pred(&w[0], &w[1]))
.map(|i| i + 1)
}
}
impl<T: Sync> ChunkBySlice<T> for &[T] {
fn split(self, index: usize) -> (Self, Self) {
self.split_at(index)
}
}
impl<T: Send> ChunkBySlice<T> for &mut [T] {
fn split(self, index: usize) -> (Self, Self) {
self.split_at_mut(index)
}
}
struct ChunkByProducer<'p, T, Slice, Pred> {
slice: Slice,
pred: &'p Pred,
tail: usize,
marker: PhantomData<fn(&T)>,
}
// Note: this implementation is very similar to `SplitProducer`.
impl<T, Slice, Pred> UnindexedProducer for ChunkByProducer<'_, T, Slice, Pred>
where
Slice: ChunkBySlice<T>,
Pred: Fn(&T, &T) -> bool + Send + Sync,
{
type Item = Slice;
fn split(self) -> (Self, Option<Self>) {
if self.tail < 2 {
return (Self { tail: 0, ..self }, None);
}
// Look forward for the separator, and failing that look backward.
let mid = self.tail / 2;
let index = match self.slice.find(self.pred, mid, self.tail) {
Some(i) => Some(mid + i),
None => self.slice.rfind(self.pred, mid + 1),
};
if let Some(index) = index {
let (left, right) = self.slice.split(index);
let (left_tail, right_tail) = if index <= mid {
// If we scanned backwards to find the separator, everything in
// the right side is exhausted, with no separators left to find.
(index, 0)
} else {
(mid + 1, self.tail - index)
};
// Create the left split before the separator.
let left = Self {
slice: left,
tail: left_tail,
..self
};
// Create the right split following the separator.
let right = Self {
slice: right,
tail: right_tail,
..self
};
(left, Some(right))
} else {
// The search is exhausted, no more separators...
(Self { tail: 0, ..self }, None)
}
}
fn fold_with<F>(self, mut folder: F) -> F
where
F: Folder<Self::Item>,
{
let Self {
slice, pred, tail, ..
} = self;
let (slice, tail) = if tail == slice.as_ref().len() {
// No tail section, so just let `consume_iter` do it all.
(Some(slice), None)
} else if let Some(index) = slice.rfind(pred, tail) {
// We found the last separator to complete the tail, so
// end with that slice after `consume_iter` finds the rest.
let (left, right) = slice.split(index);
(Some(left), Some(right))
} else {
// We know there are no separators at all, so it's all "tail".
(None, Some(slice))
};
if let Some(mut slice) = slice {
// TODO (MSRV 1.77) use either:
// folder.consume_iter(slice.chunk_by(pred))
// folder.consume_iter(slice.chunk_by_mut(pred))
folder = folder.consume_iter(std::iter::from_fn(move || {
let len = slice.as_ref().len();
if len > 0 {
let i = slice.find(pred, 0, len).unwrap_or(len);
let (head, tail) = mem::take(&mut slice).split(i);
slice = tail;
Some(head)
} else {
None
}
}));
}
if let Some(tail) = tail {
folder = folder.consume(tail);
}
folder
}
}
/// Parallel iterator over slice in (non-overlapping) chunks separated by a predicate.
///
/// This struct is created by the [`par_chunk_by`] method on `&[T]`.
///
/// [`par_chunk_by`]: trait.ParallelSlice.html#method.par_chunk_by
pub struct ChunkBy<'data, T, P> {
pred: P,
slice: &'data [T],
}
impl<'data, T, P: Clone> Clone for ChunkBy<'data, T, P> {
fn clone(&self) -> Self {
ChunkBy {
pred: self.pred.clone(),
slice: self.slice,
}
}
}
impl<'data, T: fmt::Debug, P> fmt::Debug for ChunkBy<'data, T, P> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ChunkBy")
.field("slice", &self.slice)
.finish()
}
}
impl<'data, T, P> ChunkBy<'data, T, P> {
pub(super) fn new(slice: &'data [T], pred: P) -> Self {
Self { pred, slice }
}
}
impl<'data, T, P> ParallelIterator for ChunkBy<'data, T, P>
where
T: Sync,
P: Fn(&T, &T) -> bool + Send + Sync,
{
type Item = &'data [T];
fn drive_unindexed<C>(self, consumer: C) -> C::Result
where
C: UnindexedConsumer<Self::Item>,
{
bridge_unindexed(
ChunkByProducer {
tail: self.slice.len(),
slice: self.slice,
pred: &self.pred,
marker: PhantomData,
},
consumer,
)
}
}
/// Parallel iterator over slice in (non-overlapping) mutable chunks
/// separated by a predicate.
///
/// This struct is created by the [`par_chunk_by_mut`] method on `&mut [T]`.
///
/// [`par_chunk_by_mut`]: trait.ParallelSliceMut.html#method.par_chunk_by_mut
pub struct ChunkByMut<'data, T, P> {
pred: P,
slice: &'data mut [T],
}
impl<'data, T: fmt::Debug, P> fmt::Debug for ChunkByMut<'data, T, P> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ChunkByMut")
.field("slice", &self.slice)
.finish()
}
}
impl<'data, T, P> ChunkByMut<'data, T, P> {
pub(super) fn new(slice: &'data mut [T], pred: P) -> Self {
Self { pred, slice }
}
}
impl<'data, T, P> ParallelIterator for ChunkByMut<'data, T, P>
where
T: Send,
P: Fn(&T, &T) -> bool + Send + Sync,
{
type Item = &'data mut [T];
fn drive_unindexed<C>(self, consumer: C) -> C::Result
where
C: UnindexedConsumer<Self::Item>,
{
bridge_unindexed(
ChunkByProducer {
tail: self.slice.len(),
slice: self.slice,
pred: &self.pred,
marker: PhantomData,
},
consumer,
)
}
}

View File

@@ -1,7 +1,6 @@
use crate::iter::plumbing::*;
use crate::iter::*;
use crate::math::div_round_up;
use std::cmp;
/// Parallel iterator over immutable non-overlapping chunks of a slice
#[derive(Debug)]
@@ -74,7 +73,7 @@ impl<'data, T: 'data + Sync> Producer for ChunksProducer<'data, T> {
}
fn split_at(self, index: usize) -> (Self, Self) {
let elem_index = cmp::min(index * self.chunk_size, self.slice.len());
let elem_index = Ord::min(index * self.chunk_size, self.slice.len());
let (left, right) = self.slice.split_at(elem_index);
(
ChunksProducer {
@@ -255,7 +254,7 @@ impl<'data, T: 'data + Send> Producer for ChunksMutProducer<'data, T> {
}
fn split_at(self, index: usize) -> (Self, Self) {
let elem_index = cmp::min(index * self.chunk_size, self.slice.len());
let elem_index = Ord::min(index * self.chunk_size, self.slice.len());
let (left, right) = self.slice.split_at_mut(elem_index);
(
ChunksMutProducer {

View File

@@ -5,6 +5,7 @@
//!
//! [std::slice]: https://doc.rust-lang.org/stable/std/slice/
mod chunk_by;
mod chunks;
mod mergesort;
mod quicksort;
@@ -17,11 +18,12 @@ use self::quicksort::par_quicksort;
use crate::iter::plumbing::*;
use crate::iter::*;
use crate::split_producer::*;
use std::cmp;
use std::cmp::Ordering;
use std::fmt::{self, Debug};
use std::mem;
pub use self::chunk_by::{ChunkBy, ChunkByMut};
pub use self::chunks::{Chunks, ChunksExact, ChunksExactMut, ChunksMut};
pub use self::rchunks::{RChunks, RChunksExact, RChunksExactMut, RChunksMut};
@@ -38,11 +40,11 @@ pub trait ParallelSlice<T: Sync> {
///
/// ```
/// use rayon::prelude::*;
/// let smallest = [1, 2, 3, 0, 2, 4, 8, 0, 3, 6, 9]
/// let products: Vec<_> = [1, 2, 3, 0, 2, 4, 8, 0, 3, 6, 9]
/// .par_split(|i| *i == 0)
/// .map(|numbers| numbers.iter().min().unwrap())
/// .min();
/// assert_eq!(Some(&1), smallest);
/// .map(|numbers| numbers.iter().product::<i32>())
/// .collect();
/// assert_eq!(products, [6, 64, 162]);
/// ```
fn par_split<P>(&self, separator: P) -> Split<'_, T, P>
where
@@ -54,6 +56,29 @@ pub trait ParallelSlice<T: Sync> {
}
}
/// Returns a parallel iterator over subslices separated by elements that
/// match the separator, including the matched part as a terminator.
///
/// # Examples
///
/// ```
/// use rayon::prelude::*;
/// let lengths: Vec<_> = [1, 2, 3, 0, 2, 4, 8, 0, 3, 6, 9]
/// .par_split_inclusive(|i| *i == 0)
/// .map(|numbers| numbers.len())
/// .collect();
/// assert_eq!(lengths, [4, 4, 3]);
/// ```
fn par_split_inclusive<P>(&self, separator: P) -> SplitInclusive<'_, T, P>
where
P: Fn(&T) -> bool + Sync + Send,
{
SplitInclusive {
slice: self.as_parallel_slice(),
separator,
}
}
/// Returns a parallel iterator over all contiguous windows of length
/// `window_size`. The windows overlap.
///
@@ -150,6 +175,29 @@ pub trait ParallelSlice<T: Sync> {
assert!(chunk_size != 0, "chunk_size must not be zero");
RChunksExact::new(chunk_size, self.as_parallel_slice())
}
/// Returns a parallel iterator over the slice producing non-overlapping runs
/// of elements using the predicate to separate them.
///
/// The predicate is called on two elements following themselves,
/// it means the predicate is called on `slice[0]` and `slice[1]`
/// then on `slice[1]` and `slice[2]` and so on.
///
/// # Examples
///
/// ```
/// use rayon::prelude::*;
/// let chunks: Vec<_> = [1, 2, 2, 3, 3, 3].par_chunk_by(|&x, &y| x == y).collect();
/// assert_eq!(chunks[0], &[1]);
/// assert_eq!(chunks[1], &[2, 2]);
/// assert_eq!(chunks[2], &[3, 3, 3]);
/// ```
fn par_chunk_by<F>(&self, pred: F) -> ChunkBy<'_, T, F>
where
F: Fn(&T, &T) -> bool + Send + Sync,
{
ChunkBy::new(self.as_parallel_slice(), pred)
}
}
impl<T: Sync> ParallelSlice<T> for [T] {
@@ -187,6 +235,28 @@ pub trait ParallelSliceMut<T: Send> {
}
}
/// Returns a parallel iterator over mutable subslices separated by elements
/// that match the separator, including the matched part as a terminator.
///
/// # Examples
///
/// ```
/// use rayon::prelude::*;
/// let mut array = [1, 2, 3, 0, 2, 4, 8, 0, 3, 6, 9];
/// array.par_split_inclusive_mut(|i| *i == 0)
/// .for_each(|slice| slice.reverse());
/// assert_eq!(array, [0, 3, 2, 1, 0, 8, 4, 2, 9, 6, 3]);
/// ```
fn par_split_inclusive_mut<P>(&mut self, separator: P) -> SplitInclusiveMut<'_, T, P>
where
P: Fn(&T) -> bool + Sync + Send,
{
SplitInclusiveMut {
slice: self.as_parallel_slice_mut(),
separator,
}
}
/// Returns a parallel iterator over at most `chunk_size` elements of
/// `self` at a time. The chunks are mutable and do not overlap.
///
@@ -659,6 +729,30 @@ pub trait ParallelSliceMut<T: Send> {
{
par_quicksort(self.as_parallel_slice_mut(), |a, b| f(a).lt(&f(b)));
}
/// Returns a parallel iterator over the slice producing non-overlapping mutable
/// runs of elements using the predicate to separate them.
///
/// The predicate is called on two elements following themselves,
/// it means the predicate is called on `slice[0]` and `slice[1]`
/// then on `slice[1]` and `slice[2]` and so on.
///
/// # Examples
///
/// ```
/// use rayon::prelude::*;
/// let mut xs = [1, 2, 2, 3, 3, 3];
/// let chunks: Vec<_> = xs.par_chunk_by_mut(|&x, &y| x == y).collect();
/// assert_eq!(chunks[0], &mut [1]);
/// assert_eq!(chunks[1], &mut [2, 2]);
/// assert_eq!(chunks[2], &mut [3, 3, 3]);
/// ```
fn par_chunk_by_mut<F>(&mut self, pred: F) -> ChunkByMut<'_, T, F>
where
F: Fn(&T, &T) -> bool + Send + Sync,
{
ChunkByMut::new(self.as_parallel_slice_mut(), pred)
}
}
impl<T: Send> ParallelSliceMut<T> for [T] {
@@ -817,7 +911,7 @@ impl<'data, T: 'data + Sync> Producer for WindowsProducer<'data, T> {
}
fn split_at(self, index: usize) -> (Self, Self) {
let left_index = cmp::min(self.slice.len(), index + (self.window_size - 1));
let left_index = Ord::min(self.slice.len(), index + (self.window_size - 1));
let left = &self.slice[..left_index];
let right = &self.slice[index..];
(
@@ -932,6 +1026,46 @@ where
}
}
/// Parallel iterator over slices separated by a predicate,
/// including the matched part as a terminator.
pub struct SplitInclusive<'data, T, P> {
slice: &'data [T],
separator: P,
}
impl<'data, T, P: Clone> Clone for SplitInclusive<'data, T, P> {
fn clone(&self) -> Self {
SplitInclusive {
separator: self.separator.clone(),
..*self
}
}
}
impl<'data, T: Debug, P> Debug for SplitInclusive<'data, T, P> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SplitInclusive")
.field("slice", &self.slice)
.finish()
}
}
impl<'data, T, P> ParallelIterator for SplitInclusive<'data, T, P>
where
P: Fn(&T) -> bool + Sync + Send,
T: Sync,
{
type Item = &'data [T];
fn drive_unindexed<C>(self, consumer: C) -> C::Result
where
C: UnindexedConsumer<Self::Item>,
{
let producer = SplitInclusiveProducer::new_incl(self.slice, &self.separator);
bridge_unindexed(producer, consumer)
}
}
/// Implement support for `SplitProducer`.
impl<'data, T, P> Fissile<P> for &'data [T]
where
@@ -953,21 +1087,31 @@ where
self[..end].iter().rposition(separator)
}
fn split_once(self, index: usize) -> (Self, Self) {
let (left, right) = self.split_at(index);
(left, &right[1..]) // skip the separator
fn split_once<const INCL: bool>(self, index: usize) -> (Self, Self) {
if INCL {
// include the separator in the left side
self.split_at(index + 1)
} else {
let (left, right) = self.split_at(index);
(left, &right[1..]) // skip the separator
}
}
fn fold_splits<F>(self, separator: &P, folder: F, skip_last: bool) -> F
fn fold_splits<F, const INCL: bool>(self, separator: &P, folder: F, skip_last: bool) -> F
where
F: Folder<Self>,
Self: Send,
{
let mut split = self.split(separator);
if skip_last {
split.next_back();
if INCL {
debug_assert!(!skip_last);
folder.consume_iter(self.split_inclusive(separator))
} else {
let mut split = self.split(separator);
if skip_last {
split.next_back();
}
folder.consume_iter(split)
}
folder.consume_iter(split)
}
}
@@ -1001,6 +1145,37 @@ where
}
}
/// Parallel iterator over mutable slices separated by a predicate,
/// including the matched part as a terminator.
pub struct SplitInclusiveMut<'data, T, P> {
slice: &'data mut [T],
separator: P,
}
impl<'data, T: Debug, P> Debug for SplitInclusiveMut<'data, T, P> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SplitInclusiveMut")
.field("slice", &self.slice)
.finish()
}
}
impl<'data, T, P> ParallelIterator for SplitInclusiveMut<'data, T, P>
where
P: Fn(&T) -> bool + Sync + Send,
T: Send,
{
type Item = &'data mut [T];
fn drive_unindexed<C>(self, consumer: C) -> C::Result
where
C: UnindexedConsumer<Self::Item>,
{
let producer = SplitInclusiveProducer::new_incl(self.slice, &self.separator);
bridge_unindexed(producer, consumer)
}
}
/// Implement support for `SplitProducer`.
impl<'data, T, P> Fissile<P> for &'data mut [T]
where
@@ -1022,20 +1197,30 @@ where
self[..end].iter().rposition(separator)
}
fn split_once(self, index: usize) -> (Self, Self) {
let (left, right) = self.split_at_mut(index);
(left, &mut right[1..]) // skip the separator
fn split_once<const INCL: bool>(self, index: usize) -> (Self, Self) {
if INCL {
// include the separator in the left side
self.split_at_mut(index + 1)
} else {
let (left, right) = self.split_at_mut(index);
(left, &mut right[1..]) // skip the separator
}
}
fn fold_splits<F>(self, separator: &P, folder: F, skip_last: bool) -> F
fn fold_splits<F, const INCL: bool>(self, separator: &P, folder: F, skip_last: bool) -> F
where
F: Folder<Self>,
Self: Send,
{
let mut split = self.split_mut(separator);
if skip_last {
split.next_back();
if INCL {
debug_assert!(!skip_last);
folder.consume_iter(self.split_inclusive_mut(separator))
} else {
let mut split = self.split_mut(separator);
if skip_last {
split.next_back();
}
folder.consume_iter(split)
}
folder.consume_iter(split)
}
}

View File

@@ -4,17 +4,34 @@
//! The only difference from the original is that calls to `recurse` are executed in parallel using
//! `rayon_core::join`.
use std::cmp;
use std::marker::PhantomData;
use std::mem::{self, MaybeUninit};
use std::ptr;
/// When dropped, copies from `src` into `dest`.
struct CopyOnDrop<T> {
#[must_use]
struct CopyOnDrop<'a, T> {
src: *const T,
dest: *mut T,
/// `src` is often a local pointer here, make sure we have appropriate
/// PhantomData so that dropck can protect us.
marker: PhantomData<&'a mut T>,
}
impl<T> Drop for CopyOnDrop<T> {
impl<'a, T> CopyOnDrop<'a, T> {
/// Construct from a source pointer and a destination
/// Assumes dest lives longer than src, since there is no easy way to
/// copy down lifetime information from another pointer
unsafe fn new(src: &'a T, dest: *mut T) -> Self {
CopyOnDrop {
src,
dest,
marker: PhantomData,
}
}
}
impl<T> Drop for CopyOnDrop<'_, T> {
fn drop(&mut self) {
// SAFETY: This is a helper class.
// Please refer to its usage for correctness.
@@ -54,10 +71,7 @@ where
// into the slice.
let tmp = mem::ManuallyDrop::new(ptr::read(v.get_unchecked(0)));
let v = v.as_mut_ptr();
let mut hole = CopyOnDrop {
src: &*tmp,
dest: v.add(1),
};
let mut hole = CopyOnDrop::new(&*tmp, v.add(1));
ptr::copy_nonoverlapping(v.add(1), v.add(0), 1);
for i in 2..len {
@@ -103,10 +117,7 @@ where
// into the slice.
let tmp = mem::ManuallyDrop::new(ptr::read(v.get_unchecked(len - 1)));
let v = v.as_mut_ptr();
let mut hole = CopyOnDrop {
src: &*tmp,
dest: v.add(len - 2),
};
let mut hole = CopyOnDrop::new(&*tmp, v.add(len - 2));
ptr::copy_nonoverlapping(v.add(len - 2), v.add(len - 1), 1);
for i in (0..len - 2).rev() {
@@ -366,7 +377,7 @@ where
}
// Number of out-of-order elements to swap between the left and right side.
let count = cmp::min(width(start_l, end_l), width(start_r, end_r));
let count = Ord::min(width(start_l, end_l), width(start_r, end_r));
if count > 0 {
macro_rules! left {
@@ -510,10 +521,7 @@ where
// SAFETY: `pivot` is a reference to the first element of `v`, so `ptr::read` is safe.
let tmp = mem::ManuallyDrop::new(unsafe { ptr::read(pivot) });
let _pivot_guard = CopyOnDrop {
src: &*tmp,
dest: pivot,
};
let _pivot_guard = unsafe { CopyOnDrop::new(&*tmp, pivot) };
let pivot = &*tmp;
// Find the first pair of out-of-order elements.
@@ -569,10 +577,7 @@ where
// operation panics, the pivot will be automatically written back into the slice.
// SAFETY: The pointer here is valid because it is obtained from a reference to a slice.
let tmp = mem::ManuallyDrop::new(unsafe { ptr::read(pivot) });
let _pivot_guard = CopyOnDrop {
src: &*tmp,
dest: pivot,
};
let _pivot_guard = unsafe { CopyOnDrop::new(&*tmp, pivot) };
let pivot = &*tmp;
// Now partition the slice.
@@ -805,7 +810,7 @@ where
// Partition the slice.
let (mid, was_p) = partition(v, pivot, is_less);
was_balanced = cmp::min(mid, len - mid) >= len / 8;
was_balanced = Ord::min(mid, len - mid) >= len / 8;
was_partitioned = was_p;
// Split the slice into `left`, `pivot`, and `right`.
@@ -813,7 +818,7 @@ where
let (pivot, right) = right.split_at_mut(1);
let pivot = &mut pivot[0];
if cmp::max(left.len(), right.len()) <= MAX_SEQUENTIAL {
if Ord::max(left.len(), right.len()) <= MAX_SEQUENTIAL {
// Recurse into the shorter side only in order to minimize the total number of recursive
// calls and consume less stack space. Then just continue with the longer side (this is
// akin to tail recursion).

View File

@@ -5,6 +5,7 @@ use rand::distributions::Uniform;
use rand::seq::SliceRandom;
use rand::{thread_rng, Rng};
use std::cmp::Ordering::{Equal, Greater, Less};
use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};
macro_rules! sort {
($f:ident, $name:ident) => {
@@ -168,3 +169,48 @@ fn test_par_rchunks_exact_mut_remainder() {
assert_eq!(c.take_remainder(), &[]);
assert_eq!(c.len(), 2);
}
#[test]
fn slice_chunk_by() {
let v: Vec<_> = (0..1000).collect();
assert_eq!(v[..0].par_chunk_by(|_, _| todo!()).count(), 0);
assert_eq!(v[..1].par_chunk_by(|_, _| todo!()).count(), 1);
assert_eq!(v[..2].par_chunk_by(|_, _| true).count(), 1);
assert_eq!(v[..2].par_chunk_by(|_, _| false).count(), 2);
let count = AtomicUsize::new(0);
let par: Vec<_> = v
.par_chunk_by(|x, y| {
count.fetch_add(1, Relaxed);
(x % 10 < 3) == (y % 10 < 3)
})
.collect();
assert_eq!(count.into_inner(), v.len() - 1);
let seq: Vec<_> = v.chunk_by(|x, y| (x % 10 < 3) == (y % 10 < 3)).collect();
assert_eq!(par, seq);
}
#[test]
fn slice_chunk_by_mut() {
let mut v: Vec<_> = (0..1000).collect();
assert_eq!(v[..0].par_chunk_by_mut(|_, _| todo!()).count(), 0);
assert_eq!(v[..1].par_chunk_by_mut(|_, _| todo!()).count(), 1);
assert_eq!(v[..2].par_chunk_by_mut(|_, _| true).count(), 1);
assert_eq!(v[..2].par_chunk_by_mut(|_, _| false).count(), 2);
let mut v2 = v.clone();
let count = AtomicUsize::new(0);
let par: Vec<_> = v
.par_chunk_by_mut(|x, y| {
count.fetch_add(1, Relaxed);
(x % 10 < 3) == (y % 10 < 3)
})
.collect();
assert_eq!(count.into_inner(), v2.len() - 1);
let seq: Vec<_> = v2
.chunk_by_mut(|x, y| (x % 10 < 3) == (y % 10 < 3))
.collect();
assert_eq!(par, seq);
}

View File

@@ -5,7 +5,7 @@
use crate::iter::plumbing::{Folder, UnindexedProducer};
/// Common producer for splitting on a predicate.
pub(super) struct SplitProducer<'p, P, V> {
pub(super) struct SplitProducer<'p, P, V, const INCL: bool = false> {
data: V,
separator: &'p P,
@@ -13,14 +13,16 @@ pub(super) struct SplitProducer<'p, P, V> {
tail: usize,
}
pub(super) type SplitInclusiveProducer<'p, P, V> = SplitProducer<'p, P, V, true>;
/// Helper trait so `&str`, `&[T]`, and `&mut [T]` can share `SplitProducer`.
pub(super) trait Fissile<P>: Sized {
fn length(&self) -> usize;
fn midpoint(&self, end: usize) -> usize;
fn find(&self, separator: &P, start: usize, end: usize) -> Option<usize>;
fn rfind(&self, separator: &P, end: usize) -> Option<usize>;
fn split_once(self, index: usize) -> (Self, Self);
fn fold_splits<F>(self, separator: &P, folder: F, skip_last: bool) -> F
fn split_once<const INCL: bool>(self, index: usize) -> (Self, Self);
fn fold_splits<F, const INCL: bool>(self, separator: &P, folder: F, skip_last: bool) -> F
where
F: Folder<Self>,
Self: Send;
@@ -37,7 +39,25 @@ where
separator,
}
}
}
impl<'p, P, V> SplitInclusiveProducer<'p, P, V>
where
V: Fissile<P> + Send,
{
pub(super) fn new_incl(data: V, separator: &'p P) -> Self {
SplitProducer {
tail: data.length(),
data,
separator,
}
}
}
impl<'p, P, V, const INCL: bool> SplitProducer<'p, P, V, INCL>
where
V: Fissile<P> + Send,
{
/// Common `fold_with` implementation, integrating `SplitTerminator`'s
/// need to sometimes skip its final empty item.
pub(super) fn fold_with<F>(self, folder: F, skip_last: bool) -> F
@@ -52,12 +72,12 @@ where
if tail == data.length() {
// No tail section, so just let `fold_splits` handle it.
data.fold_splits(separator, folder, skip_last)
data.fold_splits::<F, INCL>(separator, folder, skip_last)
} else if let Some(index) = data.rfind(separator, tail) {
// We found the last separator to complete the tail, so
// end with that slice after `fold_splits` finds the rest.
let (left, right) = data.split_once(index);
let folder = left.fold_splits(separator, folder, false);
let (left, right) = data.split_once::<INCL>(index);
let folder = left.fold_splits::<F, INCL>(separator, folder, false);
if skip_last || folder.full() {
folder
} else {
@@ -74,7 +94,7 @@ where
}
}
impl<'p, P, V> UnindexedProducer for SplitProducer<'p, P, V>
impl<'p, P, V, const INCL: bool> UnindexedProducer for SplitProducer<'p, P, V, INCL>
where
V: Fissile<P> + Send,
P: Sync,
@@ -91,7 +111,7 @@ where
if let Some(index) = index {
let len = self.data.length();
let (left, right) = self.data.split_once(index);
let (left, right) = self.data.split_once::<INCL>(index);
let (left_tail, right_tail) = if index < mid {
// If we scanned backwards to find the separator, everything in

View File

@@ -6,8 +6,8 @@
//! Note: [`ParallelString::par_split()`] and [`par_split_terminator()`]
//! reference a `Pattern` trait which is not visible outside this crate.
//! This trait is intentionally kept private, for use only by Rayon itself.
//! It is implemented for `char`, `&[char]`, and any function or closure
//! `F: Fn(char) -> bool + Sync + Send`.
//! It is implemented for `char`, `&[char]`, `[char; N]`, `&[char; N]`,
//! and any function or closure `F: Fn(char) -> bool + Sync + Send`.
//!
//! [`ParallelString::par_split()`]: trait.ParallelString.html#method.par_split
//! [`par_split_terminator()`]: trait.ParallelString.html#method.par_split_terminator
@@ -140,8 +140,8 @@ pub trait ParallelString {
/// given character or predicate, similar to `str::split`.
///
/// Note: the `Pattern` trait is private, for use only by Rayon itself.
/// It is implemented for `char`, `&[char]`, and any function or closure
/// `F: Fn(char) -> bool + Sync + Send`.
/// It is implemented for `char`, `&[char]`, `[char; N]`, `&[char; N]`,
/// and any function or closure `F: Fn(char) -> bool + Sync + Send`.
///
/// # Examples
///
@@ -157,14 +157,35 @@ pub trait ParallelString {
Split::new(self.as_parallel_string(), separator)
}
/// Returns a parallel iterator over substrings separated by a
/// given character or predicate, keeping the matched part as a terminator
/// of the substring similar to `str::split_inclusive`.
///
/// Note: the `Pattern` trait is private, for use only by Rayon itself.
/// It is implemented for `char`, `&[char]`, `[char; N]`, `&[char; N]`,
/// and any function or closure `F: Fn(char) -> bool + Sync + Send`.
///
/// # Examples
///
/// ```
/// use rayon::prelude::*;
/// let lines: Vec<_> = "Mary had a little lamb\nlittle lamb\nlittle lamb."
/// .par_split_inclusive('\n')
/// .collect();
/// assert_eq!(lines, ["Mary had a little lamb\n", "little lamb\n", "little lamb."]);
/// ```
fn par_split_inclusive<P: Pattern>(&self, separator: P) -> SplitInclusive<'_, P> {
SplitInclusive::new(self.as_parallel_string(), separator)
}
/// Returns a parallel iterator over substrings terminated by a
/// given character or predicate, similar to `str::split_terminator`.
/// It's equivalent to `par_split`, except it doesn't produce an empty
/// substring after a trailing terminator.
///
/// Note: the `Pattern` trait is private, for use only by Rayon itself.
/// It is implemented for `char`, `&[char]`, and any function or closure
/// `F: Fn(char) -> bool + Sync + Send`.
/// It is implemented for `char`, `&[char]`, `[char; N]`, `&[char; N]`,
/// and any function or closure `F: Fn(char) -> bool + Sync + Send`.
///
/// # Examples
///
@@ -203,6 +224,8 @@ pub trait ParallelString {
///
/// As with `str::split_whitespace`, 'whitespace' is defined according to
/// the terms of the Unicode Derived Core Property `White_Space`.
/// If you only want to split on ASCII whitespace instead, use
/// [`par_split_ascii_whitespace`][`ParallelString::par_split_ascii_whitespace`].
///
/// # Examples
///
@@ -213,16 +236,71 @@ pub trait ParallelString {
/// .max_by_key(|word| word.len());
/// assert_eq!(Some("longest"), longest);
/// ```
///
/// All kinds of whitespace are considered:
///
/// ```
/// use rayon::prelude::*;
/// let words: Vec<&str> = " Mary had\ta\u{2009}little \n\t lamb"
/// .par_split_whitespace()
/// .collect();
/// assert_eq!(words, ["Mary", "had", "a", "little", "lamb"]);
/// ```
///
/// If the string is empty or all whitespace, the iterator yields no string slices:
///
/// ```
/// use rayon::prelude::*;
/// assert_eq!("".par_split_whitespace().count(), 0);
/// assert_eq!(" ".par_split_whitespace().count(), 0);
/// ```
fn par_split_whitespace(&self) -> SplitWhitespace<'_> {
SplitWhitespace(self.as_parallel_string())
}
/// Returns a parallel iterator over the sub-slices of a string that are
/// separated by any amount of ASCII whitespace.
///
/// To split by Unicode `White_Space` instead, use
/// [`par_split_whitespace`][`ParallelString::par_split_whitespace`].
///
/// # Examples
///
/// ```
/// use rayon::prelude::*;
/// let longest = "which is the longest word?"
/// .par_split_ascii_whitespace()
/// .max_by_key(|word| word.len());
/// assert_eq!(Some("longest"), longest);
/// ```
///
/// All kinds of ASCII whitespace are considered, but not Unicode `White_Space`:
///
/// ```
/// use rayon::prelude::*;
/// let words: Vec<&str> = " Mary had\ta\u{2009}little \n\t lamb"
/// .par_split_ascii_whitespace()
/// .collect();
/// assert_eq!(words, ["Mary", "had", "a\u{2009}little", "lamb"]);
/// ```
///
/// If the string is empty or all ASCII whitespace, the iterator yields no string slices:
///
/// ```
/// use rayon::prelude::*;
/// assert_eq!("".par_split_whitespace().count(), 0);
/// assert_eq!(" ".par_split_whitespace().count(), 0);
/// ```
fn par_split_ascii_whitespace(&self) -> SplitAsciiWhitespace<'_> {
SplitAsciiWhitespace(self.as_parallel_string())
}
/// Returns a parallel iterator over substrings that match a
/// given character or predicate, similar to `str::matches`.
///
/// Note: the `Pattern` trait is private, for use only by Rayon itself.
/// It is implemented for `char`, `&[char]`, and any function or closure
/// `F: Fn(char) -> bool + Sync + Send`.
/// It is implemented for `char`, `&[char]`, `[char; N]`, `&[char; N]`,
/// and any function or closure `F: Fn(char) -> bool + Sync + Send`.
///
/// # Examples
///
@@ -245,8 +323,8 @@ pub trait ParallelString {
/// or predicate, with their positions, similar to `str::match_indices`.
///
/// Note: the `Pattern` trait is private, for use only by Rayon itself.
/// It is implemented for `char`, `&[char]`, and any function or closure
/// `F: Fn(char) -> bool + Sync + Send`.
/// It is implemented for `char`, `&[char]`, `[char; N]`, `&[char; N]`,
/// and any function or closure `F: Fn(char) -> bool + Sync + Send`.
///
/// # Examples
///
@@ -291,6 +369,9 @@ mod private {
fn rfind_in(&self, haystack: &str) -> Option<usize>;
fn is_suffix_of(&self, haystack: &str) -> bool;
fn fold_splits<'ch, F>(&self, haystack: &'ch str, folder: F, skip_last: bool) -> F
where
F: Folder<&'ch str>;
fn fold_inclusive_splits<'ch, F>(&self, haystack: &'ch str, folder: F) -> F
where
F: Folder<&'ch str>;
fn fold_matches<'ch, F>(&self, haystack: &'ch str, folder: F) -> F
@@ -338,6 +419,13 @@ macro_rules! impl_pattern {
folder.consume_iter(split)
}
fn fold_inclusive_splits<'ch, F>(&$self, chars: &'ch str, folder: F) -> F
where
F: Folder<&'ch str>,
{
folder.consume_iter(chars.split_inclusive($pattern))
}
fn fold_matches<'ch, F>(&$self, chars: &'ch str, folder: F) -> F
where
F: Folder<&'ch str>,
@@ -362,6 +450,17 @@ impl Pattern for &[char] {
impl_pattern!(&self => *self);
}
// TODO (MSRV 1.75): use `*self` for array patterns too.
// - Needs `DoubleEndedSearcher` so `split.next_back()` works.
impl<const N: usize> Pattern for [char; N] {
impl_pattern!(&self => self.as_slice());
}
impl<const N: usize> Pattern for &[char; N] {
impl_pattern!(&self => self.as_slice());
}
impl<FN: Sync + Send + Fn(char) -> bool> Pattern for FN {
impl_pattern!(&self => self);
}
@@ -600,18 +699,56 @@ impl<'ch, P: Pattern> Fissile<P> for &'ch str {
separator.rfind_in(&self[..end])
}
fn split_once(self, index: usize) -> (Self, Self) {
let (left, right) = self.split_at(index);
let mut right_iter = right.chars();
right_iter.next(); // skip the separator
(left, right_iter.as_str())
fn split_once<const INCL: bool>(self, index: usize) -> (Self, Self) {
if INCL {
// include the separator in the left side
let separator = self[index..].chars().next().unwrap();
self.split_at(index + separator.len_utf8())
} else {
let (left, right) = self.split_at(index);
let mut right_iter = right.chars();
right_iter.next(); // skip the separator
(left, right_iter.as_str())
}
}
fn fold_splits<F>(self, separator: &P, folder: F, skip_last: bool) -> F
fn fold_splits<F, const INCL: bool>(self, separator: &P, folder: F, skip_last: bool) -> F
where
F: Folder<Self>,
{
separator.fold_splits(self, folder, skip_last)
if INCL {
debug_assert!(!skip_last);
separator.fold_inclusive_splits(self, folder)
} else {
separator.fold_splits(self, folder, skip_last)
}
}
}
// /////////////////////////////////////////////////////////////////////////
/// Parallel iterator over substrings separated by a pattern
#[derive(Debug, Clone)]
pub struct SplitInclusive<'ch, P: Pattern> {
chars: &'ch str,
separator: P,
}
impl<'ch, P: Pattern> SplitInclusive<'ch, P> {
fn new(chars: &'ch str, separator: P) -> Self {
SplitInclusive { chars, separator }
}
}
impl<'ch, P: Pattern> ParallelIterator for SplitInclusive<'ch, P> {
type Item = &'ch str;
fn drive_unindexed<C>(self, consumer: C) -> C::Result
where
C: UnindexedConsumer<Self::Item>,
{
let producer = SplitInclusiveProducer::new_incl(self.chars, &self.separator);
bridge_unindexed(producer, consumer)
}
}
@@ -733,6 +870,31 @@ impl<'ch> ParallelIterator for SplitWhitespace<'ch> {
// /////////////////////////////////////////////////////////////////////////
/// Parallel iterator over substrings separated by ASCII whitespace
#[derive(Debug, Clone)]
pub struct SplitAsciiWhitespace<'ch>(&'ch str);
#[inline]
fn is_ascii_whitespace(c: char) -> bool {
c.is_ascii_whitespace()
}
impl<'ch> ParallelIterator for SplitAsciiWhitespace<'ch> {
type Item = &'ch str;
fn drive_unindexed<C>(self, consumer: C) -> C::Result
where
C: UnindexedConsumer<Self::Item>,
{
self.0
.par_split(is_ascii_whitespace)
.filter(not_empty)
.drive_unindexed(consumer)
}
}
// /////////////////////////////////////////////////////////////////////////
/// Parallel iterator over substrings that match a pattern
#[derive(Debug, Clone)]
pub struct Matches<'ch, P: Pattern> {

View File

@@ -225,8 +225,9 @@ impl<'data, T: 'data + Send> Producer for DrainProducer<'data, T> {
impl<'data, T: 'data + Send> Drop for DrainProducer<'data, T> {
fn drop(&mut self) {
// use `Drop for [T]`
unsafe { ptr::drop_in_place(self.slice) };
// extract the slice so we can use `Drop for [T]`
let slice_ptr: *mut [T] = mem::take::<&'data mut [T]>(&mut self.slice);
unsafe { ptr::drop_in_place::<[T]>(slice_ptr) };
}
}
@@ -276,7 +277,7 @@ impl<'data, T: 'data> iter::FusedIterator for SliceDrain<'data, T> {}
impl<'data, T: 'data> Drop for SliceDrain<'data, T> {
fn drop(&mut self) {
// extract the iterator so we can use `Drop for [T]`
let iter = mem::replace(&mut self.iter, [].iter_mut());
unsafe { ptr::drop_in_place(iter.into_slice()) };
let slice_ptr: *mut [T] = mem::replace(&mut self.iter, [].iter_mut()).into_slice();
unsafe { ptr::drop_in_place::<[T]>(slice_ptr) };
}
}

View File

@@ -1,5 +1,4 @@
use rayon::prelude::*;
use std::char;
#[test]
fn half_open_correctness() {

View File

@@ -10,6 +10,13 @@ where
assert_eq!(a, b);
}
fn check_count<I>(iter: I)
where
I: ParallelIterator + Clone,
{
assert_eq!(iter.clone().count(), iter.count());
}
#[test]
fn clone_binary_heap() {
use std::collections::BinaryHeap;
@@ -92,20 +99,24 @@ fn clone_str() {
check(s.par_chars());
check(s.par_lines());
check(s.par_split('\n'));
check(s.par_split_inclusive('\n'));
check(s.par_split_terminator('\n'));
check(s.par_split_whitespace());
check(s.par_split_ascii_whitespace());
}
#[test]
fn clone_vec() {
let v: Vec<_> = (0..1000).collect();
check(v.par_iter());
check(v.par_chunk_by(i32::eq));
check(v.par_chunks(42));
check(v.par_chunks_exact(42));
check(v.par_rchunks(42));
check(v.par_rchunks_exact(42));
check(v.par_windows(42));
check(v.par_split(|x| x % 3 == 0));
check(v.par_split_inclusive(|x| x % 3 == 0));
check(v.into_par_iter());
}
@@ -118,6 +129,8 @@ fn clone_array() {
#[test]
fn clone_adaptors() {
let v: Vec<_> = (0..1000).map(Some).collect();
check(v.par_iter().by_exponential_blocks());
check(v.par_iter().by_uniform_blocks(100));
check(v.par_iter().chain(&v));
check(v.par_iter().cloned());
check(v.par_iter().copied());
@@ -150,8 +163,10 @@ fn clone_adaptors() {
check(v.par_iter().panic_fuse());
check(v.par_iter().positions(|_| true));
check(v.par_iter().rev());
check(v.par_iter().skip(1));
check(v.par_iter().take(1));
check(v.par_iter().skip(42));
check(v.par_iter().skip_any_while(|_| false));
check(v.par_iter().take(42));
check(v.par_iter().take_any_while(|_| true));
check(v.par_iter().cloned().while_some());
check(v.par_iter().with_max_len(1));
check(v.par_iter().with_min_len(1));
@@ -160,6 +175,13 @@ fn clone_adaptors() {
check(v.par_iter().step_by(2));
}
#[test]
fn clone_counted_adaptors() {
let v: Vec<_> = (0..1000).collect();
check_count(v.par_iter().skip_any(42));
check_count(v.par_iter().take_any(42));
}
#[test]
fn clone_empty() {
check(rayon::iter::empty::<i32>());

View File

@@ -6,6 +6,7 @@ use std::sync::atomic::Ordering;
use std::sync::Mutex;
#[test]
#[cfg_attr(not(panic = "unwind"), ignore)]
fn collect_drop_on_unwind() {
struct Recorddrop<'a>(i64, &'a Mutex<Vec<i64>>);
@@ -61,6 +62,7 @@ fn collect_drop_on_unwind() {
}
#[test]
#[cfg_attr(not(panic = "unwind"), ignore)]
fn collect_drop_on_unwind_zst() {
static INSERTS: AtomicUsize = AtomicUsize::new(0);
static DROPS: AtomicUsize = AtomicUsize::new(0);

View File

@@ -2,6 +2,7 @@ use rayon::prelude::*;
use rayon::ThreadPoolBuilder;
#[test]
#[cfg_attr(any(target_os = "emscripten", target_family = "wasm"), ignore)]
fn cross_pool_busy() {
let pool1 = ThreadPoolBuilder::new().num_threads(1).build().unwrap();
let pool2 = ThreadPoolBuilder::new().num_threads(1).build().unwrap();

View File

@@ -104,8 +104,10 @@ fn debug_str() {
check(s.par_chars());
check(s.par_lines());
check(s.par_split('\n'));
check(s.par_split_inclusive('\n'));
check(s.par_split_terminator('\n'));
check(s.par_split_whitespace());
check(s.par_split_ascii_whitespace());
}
#[test]
@@ -119,6 +121,8 @@ fn debug_vec() {
let mut v: Vec<_> = (0..10).collect();
check(v.par_iter());
check(v.par_iter_mut());
check(v.par_chunk_by(i32::eq));
check(v.par_chunk_by_mut(i32::eq));
check(v.par_chunks(42));
check(v.par_chunks_exact(42));
check(v.par_chunks_mut(42));
@@ -129,7 +133,9 @@ fn debug_vec() {
check(v.par_rchunks_exact_mut(42));
check(v.par_windows(42));
check(v.par_split(|x| x % 3 == 0));
check(v.par_split_inclusive(|x| x % 3 == 0));
check(v.par_split_mut(|x| x % 3 == 0));
check(v.par_split_inclusive_mut(|x| x % 3 == 0));
check(v.par_drain(..));
check(v.into_par_iter());
}
@@ -143,6 +149,8 @@ fn debug_array() {
#[test]
fn debug_adaptors() {
let v: Vec<_> = (0..10).collect();
check(v.par_iter().by_exponential_blocks());
check(v.par_iter().by_uniform_blocks(5));
check(v.par_iter().chain(&v));
check(v.par_iter().cloned());
check(v.par_iter().copied());
@@ -172,7 +180,11 @@ fn debug_adaptors() {
check(v.par_iter().positions(|_| true));
check(v.par_iter().rev());
check(v.par_iter().skip(1));
check(v.par_iter().skip_any(1));
check(v.par_iter().skip_any_while(|_| false));
check(v.par_iter().take(1));
check(v.par_iter().take_any(1));
check(v.par_iter().take_any_while(|_| true));
check(v.par_iter().map(Some).while_some());
check(v.par_iter().with_max_len(1));
check(v.par_iter().with_min_len(1));

View File

@@ -20,6 +20,7 @@ fn iter_panic() {
}
#[test]
#[cfg_attr(not(panic = "unwind"), ignore)]
fn iter_panic_fuse() {
// We only use a single thread in order to make the behavior
// of 'panic_fuse' deterministic

View File

@@ -4,6 +4,7 @@ use rayon::prelude::*;
use rayon::*;
#[test]
#[cfg_attr(any(target_os = "emscripten", target_family = "wasm"), ignore)]
fn named_threads() {
ThreadPoolBuilder::new()
.thread_name(|i| format!("hello-name-test-{}", i))

View File

@@ -68,7 +68,14 @@ fn two_threads<F: Send + FnOnce() -> R, R: Send>(f: F) -> R {
}
#[test]
#[cfg_attr(not(target_pointer_width = "64"), ignore)]
#[cfg_attr(
any(
not(target_pointer_width = "64"),
target_os = "emscripten",
target_family = "wasm"
),
ignore
)]
fn find_last_octillion() {
// It would be nice if `find_last` could prioritize the later splits,
// basically flipping the `join` args, without needing indexed `rev`.
@@ -78,32 +85,49 @@ fn find_last_octillion() {
}
#[test]
#[cfg_attr(not(target_pointer_width = "64"), ignore)]
#[cfg_attr(
any(
not(target_pointer_width = "64"),
target_os = "emscripten",
target_family = "wasm"
),
ignore
)]
fn find_last_octillion_inclusive() {
let x = two_threads(|| octillion_inclusive().find_last(|_| true));
assert_eq!(x, Some(OCTILLION));
}
#[test]
#[cfg_attr(not(target_pointer_width = "64"), ignore)]
#[cfg_attr(
any(
not(target_pointer_width = "64"),
target_os = "emscripten",
target_family = "wasm"
),
ignore
)]
fn find_last_octillion_flat() {
let x = two_threads(|| octillion_flat().find_last(|_| true));
assert_eq!(x, Some(OCTILLION - 1));
}
#[test]
#[cfg_attr(any(target_os = "emscripten", target_family = "wasm"), ignore)]
fn find_any_octillion() {
let x = two_threads(|| octillion().find_any(|x| *x > OCTILLION / 2));
assert!(x.is_some());
}
#[test]
#[cfg_attr(any(target_os = "emscripten", target_family = "wasm"), ignore)]
fn find_any_octillion_flat() {
let x = two_threads(|| octillion_flat().find_any(|x| *x > OCTILLION / 2));
assert!(x.is_some());
}
#[test]
#[cfg_attr(any(target_os = "emscripten", target_family = "wasm"), ignore)]
fn filter_find_any_octillion() {
let x = two_threads(|| {
octillion()
@@ -114,6 +138,7 @@ fn filter_find_any_octillion() {
}
#[test]
#[cfg_attr(any(target_os = "emscripten", target_family = "wasm"), ignore)]
fn filter_find_any_octillion_flat() {
let x = two_threads(|| {
octillion_flat()
@@ -124,6 +149,7 @@ fn filter_find_any_octillion_flat() {
}
#[test]
#[cfg_attr(any(target_os = "emscripten", target_family = "wasm"), ignore)]
fn fold_find_any_octillion_flat() {
let x = two_threads(|| octillion_flat().fold(|| (), |_, _| ()).find_any(|_| true));
assert!(x.is_some());

View File

@@ -4,6 +4,7 @@ use std::iter::once_with;
const N: usize = 100_000;
#[test]
#[cfg_attr(any(target_os = "emscripten", target_family = "wasm"), ignore)]
fn par_bridge_recursion() {
let pool = rayon::ThreadPoolBuilder::new()
.num_threads(10)

View File

@@ -116,20 +116,17 @@ fn array() {
#[test]
fn empty() {
let v = vec![42];
check(&v[..0], rayon::iter::empty);
check(&[], rayon::iter::empty::<i32>);
}
#[test]
fn once() {
let v = vec![42];
check(&v, || rayon::iter::once(42));
check(&[42], || rayon::iter::once(42));
}
#[test]
fn option() {
let v = vec![42];
check(&v, || Some(42));
check(&[42], || Some(42));
}
#[test]

View File

@@ -2,17 +2,20 @@ use rand::distributions::Uniform;
use rand::{thread_rng, Rng};
use rayon::prelude::*;
use std::cell::Cell;
use std::cmp::{self, Ordering};
use std::cmp::Ordering;
use std::panic;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering::Relaxed;
use std::thread;
static VERSIONS: AtomicUsize = AtomicUsize::new(0);
// const is needed for array initializer
#[allow(clippy::declare_interior_mutable_const)]
const ZERO: AtomicUsize = AtomicUsize::new(0);
const LEN: usize = 20_000;
lazy_static::lazy_static! {
static ref DROP_COUNTS: Vec<AtomicUsize> = (0..20_000).map(|_| AtomicUsize::new(0)).collect();
}
static VERSIONS: AtomicUsize = ZERO;
static DROP_COUNTS: [AtomicUsize; LEN] = [ZERO; LEN];
#[derive(Clone, Eq)]
struct DropCounter {
@@ -27,6 +30,7 @@ impl PartialEq for DropCounter {
}
}
#[allow(clippy::non_canonical_partial_ord_impl)]
impl PartialOrd for DropCounter {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.version.set(self.version.get() + 1);
@@ -65,7 +69,7 @@ macro_rules! test {
let step = if len <= 100 {
1
} else {
cmp::max(1, panic_countdown / 10)
Ord::max(1, panic_countdown / 10)
};
// ... and then panic after each `step` comparisons.
@@ -114,9 +118,10 @@ macro_rules! test {
};
}
thread_local!(static SILENCE_PANIC: Cell<bool> = Cell::new(false));
thread_local!(static SILENCE_PANIC: Cell<bool> = const { Cell::new(false) });
#[test]
#[cfg_attr(any(target_os = "emscripten", target_family = "wasm"), ignore)]
fn sort_panic_safe() {
let prev = panic::take_hook();
panic::set_hook(Box::new(move |info| {
@@ -144,8 +149,8 @@ fn sort_panic_safe() {
}
for _ in 0..5 {
let a = rng.sample(&len_dist);
let b = rng.sample(&len_dist);
let a = rng.sample(len_dist);
let b = rng.sample(len_dist);
if a < b {
input[a..b].reverse();
} else {

View File

@@ -56,38 +56,44 @@ pub fn execute_strings_split() {
("foo\nbar\n\r\nbaz", '\n'),
("A few words", ' '),
(" Mary had\ta\u{2009}little \n\t lamb", ' '),
("Mary had a little lamb\nlittle lamb\nlittle lamb.", '\n'),
("Mary had a little lamb\nlittle lamb\nlittle lamb.\n", '\n'),
(include_str!("str.rs"), ' '),
];
for &(string, separator) in &tests {
let serial: Vec<_> = string.split(separator).collect();
let parallel: Vec<_> = string.par_split(separator).collect();
assert_eq!(serial, parallel);
macro_rules! check_separators {
($split:ident, $par_split:ident) => {
for &(string, separator) in &tests {
let serial: Vec<_> = string.$split(separator).collect();
let parallel: Vec<_> = string.$par_split(separator).collect();
assert_eq!(serial, parallel);
let pattern: &[char] = &['\u{0}', separator, '\u{1F980}'];
let serial: Vec<_> = string.split(pattern).collect();
let parallel: Vec<_> = string.par_split(pattern).collect();
assert_eq!(serial, parallel);
let array = ['\u{0}', separator, '\u{1F980}'];
let array_ref = &array;
let slice: &[char] = array_ref;
let serial_fn: Vec<_> = string.split(|c| c == separator).collect();
let parallel_fn: Vec<_> = string.par_split(|c| c == separator).collect();
assert_eq!(serial_fn, parallel_fn);
let serial: Vec<_> = string.$split(slice).collect();
let parallel: Vec<_> = string.$par_split(slice).collect();
assert_eq!(serial, parallel);
let serial: Vec<_> = string.$split(array).collect();
let parallel: Vec<_> = string.$par_split(array).collect();
assert_eq!(serial, parallel);
let serial: Vec<_> = string.$split(array_ref).collect();
let parallel: Vec<_> = string.$par_split(array_ref).collect();
assert_eq!(serial, parallel);
let serial_fn: Vec<_> = string.$split(|c| c == separator).collect();
let parallel_fn: Vec<_> = string.$par_split(|c| c == separator).collect();
assert_eq!(serial_fn, parallel_fn);
}
};
}
for &(string, separator) in &tests {
let serial: Vec<_> = string.split_terminator(separator).collect();
let parallel: Vec<_> = string.par_split_terminator(separator).collect();
assert_eq!(serial, parallel);
let pattern: &[char] = &['\u{0}', separator, '\u{1F980}'];
let serial: Vec<_> = string.split_terminator(pattern).collect();
let parallel: Vec<_> = string.par_split_terminator(pattern).collect();
assert_eq!(serial, parallel);
let serial: Vec<_> = string.split_terminator(|c| c == separator).collect();
let parallel: Vec<_> = string.par_split_terminator(|c| c == separator).collect();
assert_eq!(serial, parallel);
}
check_separators!(split, par_split);
check_separators!(split_inclusive, par_split_inclusive);
check_separators!(split_terminator, par_split_terminator);
for &(string, _) in &tests {
let serial: Vec<_> = string.lines().collect();
@@ -101,34 +107,13 @@ pub fn execute_strings_split() {
assert_eq!(serial, parallel);
}
for &(string, _) in &tests {
let serial: Vec<_> = string.split_ascii_whitespace().collect();
let parallel: Vec<_> = string.par_split_ascii_whitespace().collect();
assert_eq!(serial, parallel);
}
// try matching separators too!
for &(string, separator) in &tests {
let serial: Vec<_> = string.matches(separator).collect();
let parallel: Vec<_> = string.par_matches(separator).collect();
assert_eq!(serial, parallel);
let pattern: &[char] = &['\u{0}', separator, '\u{1F980}'];
let serial: Vec<_> = string.matches(pattern).collect();
let parallel: Vec<_> = string.par_matches(pattern).collect();
assert_eq!(serial, parallel);
let serial_fn: Vec<_> = string.matches(|c| c == separator).collect();
let parallel_fn: Vec<_> = string.par_matches(|c| c == separator).collect();
assert_eq!(serial_fn, parallel_fn);
}
for &(string, separator) in &tests {
let serial: Vec<_> = string.match_indices(separator).collect();
let parallel: Vec<_> = string.par_match_indices(separator).collect();
assert_eq!(serial, parallel);
let pattern: &[char] = &['\u{0}', separator, '\u{1F980}'];
let serial: Vec<_> = string.match_indices(pattern).collect();
let parallel: Vec<_> = string.par_match_indices(pattern).collect();
assert_eq!(serial, parallel);
let serial_fn: Vec<_> = string.match_indices(|c| c == separator).collect();
let parallel_fn: Vec<_> = string.par_match_indices(|c| c == separator).collect();
assert_eq!(serial_fn, parallel_fn);
}
check_separators!(matches, par_matches);
check_separators!(match_indices, par_match_indices);
}