Rayon: Running parallel to `par_iter`

I've put up some code on the playground that hopefully expresses what I want.

I have a par_iter loop, that runs some expensive stuff, which borrows from a struct (which is kinda large and which I'd not like to clone). Works!

I have another expensive function (expensive2) that only has one thing in common with the first one, namely that it needs to read from the same struct. It does different things so, and more importantly, the return value is different. I could just run expensive2 before the par_iter loop, and functionally everything would work out (or afterwards, for that matter).

I'd very much like to put expensive2 on the same thread pool as the par_iter though, or actually I want to run it in parallel to that (spawning another independent thread seems feasible too). But I don't really see how to do that. I can't use spawn, because I want to borrow from a local, but using a scoped threat does not seem to work nicely with the par_iter, because I'd have to wait for the scope to finish before I can start the par_iter (or vice versa).

Am I missing something, is there an easy way to do this? Thanks for any pointers!

To run expensive as an operation on the same global thread pool that par_iter uses, just use rayon::join.

let (r, res): (_, Vec<_>) = rayon::join(
    || expensive2(&v),
    || v.par_iter().map(|(s, l)| expensive(s, l)).collect(),
);

For an independent thread outside of that thread-pool, you would need some scoped threads API instead; Rust 1.63 will stabilize std::thread::scope, in the meantime the crate crossbeam has a comparable API.

let (r, res) = std::thread::scope(|s| {
    let r_thread = s.spawn(|| expensive2(&v));
    
    let res: Vec<_> = v.par_iter().map(|(s, l)| expensive(s,l)).collect();
    
    let r = r_thread.join().unwrap();
    
    (r, res)
});
2 Likes

Both look very easy, and I'm a bit baffled I could not figure that out myself! Rayon has scope itself, so I'd probably not even need crossbeam...

Thanks a lot!

Rayon’s scope is for spawning things on their thread-pool, not for spawning new system-threads. It’s basically just a more flexible API for cases not covered by join alone.

scope() is a more flexible building block compared to join(), since a loop can be used to spawn any number of tasks without recursing. However, that flexibility comes at a performance price: tasks spawned using scope() must be allocated onto the heap, whereas join() can make exclusive use of the stack. Prefer join() (or, even better, parallel iterators) where possible.

But that's what I want, I think. The par_iter will use up the system resources pretty much, so I'd just want expensive 2 on the same pool so it can properly utilize cores. Or am I misunderstanding something?

That’s why I suggested using rayon::join in the first place.


I was addressing this argument

by clarifying that rayon::scope is different from crossbeam::scope, so the reason to use rayon::scope instead of crossbeam::scope is not “rayon has scope itself”, but “I want it on the rayon thread-pool”; but in that case, your use-case is simple enough to use the less capable but more efficient rayon::join, so rayon::scope is not the “proper” tool to use here either way.

Ah ok, thanks for clarifying :wink: