-
Notifications
You must be signed in to change notification settings - Fork 560
mock up breadth_first and spawn_tail
#360
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
Conversation
|
I think now that I am re-considering the if scope.local_thread_has_pending_tasks() {
scope.spawn(closure)
} else {
closure()
}There are two reasons for this:
For example: let mut parent = some_node();
loop {
let work_items = parent.children();
// avoid spawning a task if (a) no other work is pending and (b) only one thing
if work_items.len() == 1 && !scope.local_thread_has_pending_tasks() {
parent = work_items[0];
continue;
}
for child in work_items { scope.spawn(move || process(child)); }
}@bholley -- thoughts? |
rayon-core/src/registry.rs
Outdated
| if !self.breadth_first { | ||
| self.worker.pop() | ||
| } else { | ||
| self.stealer.steal() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've just published a new version of Coco that introduces Worker::steal method. Now you can call self.worker.steal() here. This change will give some performance wins in breadth-first mode. I'd be curious to know how much :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, nice!
|
Regarding the "local thread has pending tasks" idea, I guess that the best thing would be to expose it as a free function, rather like Interestingly, I've seen this proposed elsewhere as a good heuristic for whether to spawn a new task in general. In that work, it was called "being hungry", I think. The idea was: if you have no pending tasks, then this implies that other threads are "hungry" because they've stolen all of your tasks. But if you do have pending tasks, you don't necessarily want to go generating more, since they are unlikely to be consumed. (I'm a bit wary of 'locking in' on work-stealing here, but this seems like an API that we could continue to support even if we changed to something else, even if it meant always returning false or true. And anyway I can't really imagine changing to something else.) |
|
Yes, I would prefer the accessor over spawn_tail, which would be more ergonomic for our use-case. |
|
@bholley I pushed a change to I tend to think we should mirror all of these methods at the top-level as well. The main reason that I've been hesitating (particularly with respect to But I am beginning to wonder if we will ever do this change -- or at least if we would ever do it without some form of opt-in. There doesn't seem to be much reason to do it, except to try to scale gracefully with blocking I/O tasks. And based on conversations I've had with others, that is best done via some opt-in APIs that wrap the blocking I/O, which would serve the purpose. Therefore, I think I'd like to ensure that -- to the extent it makes sense -- the API surface of (@cuviper, what do you think of this reasoning?) |
|
SGTM. FWIW I think that making the thread pools dynamic would be a pretty surprising change, and we'd probably want it to be opt-in. |
Yes, and also I'm not keen on the "default" configuration (i.e., |
|
I've mirrored the |
cuviper
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall I'm good with this. I think it's a little hard to reason about when it would be beneficial, but the docs are sufficiently squishy about promising anything that I think we can just say "measure it".
There are a few simple errors noted in CI here, though oddly only on OSX... how did Linux pass!?! The more recent CI run appears to be stuck in the queue...
rayon-core/src/thread_pool/mod.rs
Outdated
| /// | ||
| /// [m]: struct.ThreadPool.html#method.current_thread_has_pending_tasks | ||
| #[inline] | ||
| pub fn current_thread_has_pending_tasks(&self) -> Option<bool> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no &self in free functions...
rayon-core/src/lib.rs
Outdated
| mod util; | ||
|
|
||
| pub use thread_pool::ThreadPool; | ||
| pub use thread_pool::current_num_threads; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's already a current_num_threads below, and it's a little different because it reports on the global pool if you're not in a pool already.
rayon-core/src/thread_pool/mod.rs
Outdated
| if curr.is_null() { | ||
| None | ||
| } else { | ||
| Some((*curr).registry.num_threads()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The field registry is private.
rayon-core/src/registry.rs
Outdated
| } | ||
|
|
||
| #[inline] | ||
| pub fn breadth_first(&self) -> bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this method used anywhere?
19d71d7 to
7b25a39
Compare
|
@cuviper um yeah obviously I didn't really build that. I pushed a new revision that addresses your various points. I removed the new |
cuviper
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good!
These introduce two new core APIs that were suggested by @bholley for use in Servo (Stylo, in particular). They are insta-stable because I would want to make a public release so that Servo can make use of them.
The APIs are:
spawn_tail()API that is intended to be used for the last task that will be spawned. It is intended as a slight optimization on callingspawnand then immediately returning from the scope, particularly in breadth-first mode. Specifically, it will invoke the closure directly if that closure would have been the next task that the local thread executes anyway.These changes introduce an
ifinto one of the main hot-paths of Rayon, so I was a bit nervous about performance. However, keep in mind that theifis 100% predictable in normal usage (i.e., if you don't mix breadth- and depth-first modes). Both @bholley and I did measurements that suggest basically zero impact on depth-first code. Also, using the newer modes leads to a 20% improvement in performance for Stylo. The stylo measurements are documented here. My own measurements, gathered on my 8x2-core linux desktop, are visible here:In the median view, you can see some effect, but it is only a few percent, and I am not that confident in my measurement setup. =) Because of my inability to convince GNUplot to use colors etc in median mode (have to tinker with that more), it's a bit hard to tell which were affected; but if you hover the cursor over, you can see that
join_recursively,increment_all_max, andincrement_allare the ones that took more time, whereasfibonacciandincrement_all_mintook less time. All of these are micro-benchmarks.I was considering putting these behind a cargo feature (perhaps one enabled by default), so that users could "opt-out" from having the
if. This seems like a plausible path, but the measurements suggest to me that it's not necessary, and it seems better to avoid having "options" if we can (so as to reduce the need to measure an exponentially increasing number of combinations).