The task structure of @threads :dynamic
and @threads :static
is actually the same, i.e. both create O(nthreads()) tasks corresponding to contiguous regions of the given (potentially large) iteration range. The only difference is that tasks can migrate (“are non-sticky”) in the former case while they can’t in the latter. Compare this to @sync for ... @spawn ...
which create one task per loop iteration and gives a form of load-balancing through Julias task scheduler. In pictures:
Note that neither :static
nor :dynamic
gives load balancing (as @spawn
does). Also note that the task->thread mapping isn’t fixed for :dynamic
but is for :static
. However, when we sort by workload we see that eventually they do the same thing. So, to summarize, which Julia thread does which chunk is dynamically decided of :dynamic
but the chunks are the same as for :static
.
(Pluto notebook: load_balancing.jl (45.8 KB) - Be aware though that I use hacky/unsafe threadid()
pattern here for simplicity.)
You might want to check out these comments by @tkf:
- Behavior of `Threads.@threads for` loop - #17 by tkf
- Feature request: a work stealing threaded for loop · Issue #21017 · JuliaLang/julia · GitHub
and the comments in these PRs: