-
-
Notifications
You must be signed in to change notification settings - Fork 5.7k
RFC: Make Threads.@threads use the PARTR scheduler #35003
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
|
Should we just deprecate |
|
Ah. Never mind then. |
|
#32477 also does this. |
|
@DilumAluthge @sync for i in 1:10_000
Threads.@spawn out[i] = sin(x[i])
end you'd find that incredibly slow because spawning each task takes on the order of 1µs. |
Ah I see, I wasn't able to decode that title when I saw it originally. If this is redundant feel free to close it. |
|
Is there an argument to be made that this is desirable independent of #32477? I found the old version of the I think it's nice that this PR takes advantage of our already provided |
base/threadingconstructs.jl
Outdated
| @sync for $rng in $(Iterators.partition)($iter, $(length)($iter) ÷ $(nthreads)()) | ||
| Base.Threads.@spawn begin | ||
| for $loopvar in $rng |
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.
Strictly speaking, I think this is a breaking change as the following code will behave differently before and after this PR:
@threads for ...
@spawn ...
endBefore this PR, @spawn is not synchronized at the end of @threads block. After this PR, it is not thread-safe: #34666. You can make it thread-safe by adding @sync before for $loopvar in $rng although the behavior of @spawn is still different (it's synchronized by @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.
Mhm, this could conceivably change behaviour (though threading is experimental, so breaking changes are allowed), but before if you did
@threads for ...
@spawn ...
endthen the @spawn would destructively interfere with the tasks spawned by @threads so arguably, there's not really any reason to have done that.
Are you able to cook up a valid test case that would have passed on the old version of @threads but would fail on this version?
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 I see an error in the test suite provides an example of exactly what you referenced. Trying to fix now.
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.
(though threading is experimental, so breaking changes are allowed)
Yeah, I think I agree. Though it could be surprising if @threads acts implicitly as @sync.
FWIW, I think Threads.foreach would be a much better API as it doesn't hide the fact that you'll have a closure. It's also easy to add keyword options, e.g., for controlling the base case size for better load balancing. Something like this:
function Threads.foreach(f, xs::AbstractArray; basesize = length(xs) ÷ Threads.nthreads())
@sync for p in Iterators.partition(xs, basesize)
@spawn foreach(f, p)
end
return
end(I'm experimenting this API here: https://github.com/tkf/ThreadsX.jl/blob/1b2f2a42094986361785b617c064e5fe8cda93af/src/map.jl#L26)
@threads can exist as a simple wrapper around Threads.foreach for backward compatibility. This trivially avoids the @sync scoping problem.
|
I think it's been mentioned elsewhere, but just wanted to add a voice that this would indeed be breaking; a few of us rely on the current behavior in order to start worker tasks on specific threads, such as the code here. I'd say as long as we have either: 1) a way to reserve a main thread where tasks couldn't be spawned, or 2) a way to still spawn a threaded task on a specific thread (e.g. |
|
Could the implementation in this pr simply live alongside the old one, under a different macro name? |
Hello, I've occassionally encountered confusion online from people who think that
Threads.@threadsis using the new depth first scheduling routine and run into problems when trying to nestThreads.@threadsloops within other multi-threaded code.I wrote up a quick macro that roughly mimics the behaviour of
Threads.@threadsbut instead usesThreads.@spawninside, but will only spawn at mostThreads.nthreads() + 1tasks.Here's an implementation of the macro that should work on Julia 1.3+ (functionally identical to the one implemented in this PR):
So that
will get turned into
This makes it so that a for loop only spawns a number of tasks equal to
Threads.nthreads()(orThreads.nthreads()+1if the division has a remainder).Here's a performance comparison on Julia 1.4.0-rc2 with
2threads:I'm not sure if it is intentional or not that the current implementation of
Threads.@threadsdoes not use PARTR and I also suspect there are things about my implementation of this macro that are not up the Base Julia snuff, hence the RFC.Thoughts, comments and code review much appreciated.