-
Notifications
You must be signed in to change notification settings - Fork 502
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
in_place_scope
documentation is confusing/unclear
#1165
Comments
Is it possible that the amount of work you spawn (5 items) is so small that it is all processed on the main thread. Please try explicitly spawning work using e.g. |
Alternatively, just to better understand this, you could run your whole program in a named thread so that the main thread can be identified by name. |
I think you misunderstand the problem; the amount of tasks or the name of the main thread does not matter. The confusion is that parallel iterators are not counted as "work that it spawns" and they're running on the global threadpool instead of the threadpool used for Using Scope::spawn does the work in the threadpool, but that was never in question. From reading the documentation on |
I think the output you posted can have an alternative explanation, i.e. working stealing just never happens (it is opportunistic that way, if the processing is already done before any stealing is possible, it does not happen) and hence all of your work happens on the main thread (not in the global thread pool). Both explanations would result in the same output and I would like to first clarify what exactly happens here. |
With a quick global threadpool it's easy to illustrate it. ThreadPoolBuilder::new()
.thread_name(|u| format!("global thread {u}"))
.build_global()
.unwrap(); I get output I expect now - but only after digging into rayon's internals and historical bug reports. Notably this seems to contradict the documentation that "work spawned" inside
|
Ok, so I tried my suggestion of naming the outer thread and indeed work stealing happens and the jobs do run on the global thread pool, not just on the main thread. So I would say this is bug in |
I would be glad if you could stick to the problem instead of making slurry suggestions as to what is difficult to understand and what is not. |
Oh, that's an even better resolution than I expected. Thanks.
Yeah, fair. |
I think there may be room for an API that overrides the "current" pool for the duration of a callback, if we can do that without too much overhead. Maybe #1166 is that, but I'll have to test it. However, that's not my expectation for what For a use case, consider if the alternate pool has a tweaked priority, for example. So with the existing behavior, you can do something like this: alt.in_place_scope(|s| {
s.spawn(|_| high_priority_work()); // send to the alt pool
stuff.par_iter().for_each(|x| regular_work(x)); // on the current "normal" pool
}); If we accept that this is useful, then primary fix here is just doc clarification, but a way to override the current pool can still be considered as a separate enhancement. In the original example here, |
Personally, I would find it clearer to explicitly install the in_place_scope(|outer| {
alt.in_place_scope(|inner| {
inner.spawn(|_| high_priority_work());
outer.install(|| stuff.par_iter().for_each(|x| regular_work(x)));
});
}); |
We don't have Your example gets quite deep on closures, and it forces Your proposed fix is also adding to the magic of the "ambient" pool. Implicit behavior has tradeoffs everywhere! |
I guess my overall feeling is that it is simpler to implement and explain that |
Indeed, I was trying for something which works with the existing API but generally I think we are missing an API to capture the current "ambient" pool and use that explicitly elsewhere. I can explicitly use a pool or I can implicitly use the global pool, but it seems hard to consistently use the global pool (or an otherwise inherited pool) explicitly.
As indicated by the test case, my main "surprise" here is that |
I pushed a trivial API extension to #1166 impl ThreadPool {
pub fn current() -> Self {
Self {
registry: Registry::current(),
}
}
pub fn with_current<F, R>(&self, f: F) -> R
where
F: FnOnce() -> R,
{
Registry::with_current(Some(&self.registry), f)
}
} which allows reifying the ambient capability and temporarily changing the current pool without "installation", i.e. the above example would then look like let default_pool = ThreadPool::current();
high_priority_pool.in_place_scope(|scope| {
scope.spawn(|_| high_priority_work());
default_pool.with_current(|| {
items.par_iter().for_each(|item| regular_work(item));
})
}); |
I was playing around with in_place_scope to try to offload some work while not blocking the main threadpool, and not blocking the worker pool with long-running main pool tasks. From reading the documentation, I thought
in_place_scope
would be a drop in replacement for scope with the behaviour I wanted.Running this code produces surprising output that, before digging deeper, appears to contradict the documentation.
I initially expected both runs to produce the same output, modulo the specific thread numbers, but I was surprised to find they ran their tasks on different pools.
From reading other issues around
in_place_scope
it's probably infeasible to change how it functions, but I was surprised by the current behaviour even after I'd read the documentation. The documentation is at least unclear if you're not aware of the internals of rayon:This is just like scope() except the closure runs on the same thread that calls in_place_scope(). Only work that it spawns runs in the thread pool.
Reading that, it's not immediately obvious that parallel iterators don't count as "spawning" and onlyscope.spawn()
calls count, and it's definitely a subtle distinction betweenin_place_scope
andinstall
/scope
.The text was updated successfully, but these errors were encountered: