224 lines
5.7 KiB
Julia
224 lines
5.7 KiB
Julia
# This file is a part of Julia. License is MIT: https://julialang.org/license
|
|
|
|
let nextidx = 0
|
|
global nextproc
|
|
function nextproc()
|
|
p = -1
|
|
if p == -1
|
|
p = workers()[(nextidx % nworkers()) + 1]
|
|
nextidx += 1
|
|
end
|
|
p
|
|
end
|
|
end
|
|
|
|
spawnat(p, thunk) = sync_add(remotecall(thunk, p))
|
|
|
|
spawn_somewhere(thunk) = spawnat(nextproc(),thunk)
|
|
|
|
macro spawn(expr)
|
|
thunk = esc(:(()->($expr)))
|
|
:(spawn_somewhere($thunk))
|
|
end
|
|
|
|
macro spawnat(p, expr)
|
|
thunk = esc(:(()->($expr)))
|
|
:(spawnat($(esc(p)), $thunk))
|
|
end
|
|
|
|
"""
|
|
@fetch
|
|
|
|
Equivalent to `fetch(@spawn expr)`.
|
|
See [`fetch`](@ref) and [`@spawn`](@ref).
|
|
"""
|
|
macro fetch(expr)
|
|
thunk = esc(:(()->($expr)))
|
|
:(remotecall_fetch($thunk, nextproc()))
|
|
end
|
|
|
|
"""
|
|
@fetchfrom
|
|
|
|
Equivalent to `fetch(@spawnat p expr)`.
|
|
See [`fetch`](@ref) and [`@spawnat`](@ref).
|
|
"""
|
|
macro fetchfrom(p, expr)
|
|
thunk = esc(:(()->($expr)))
|
|
:(remotecall_fetch($thunk, $(esc(p))))
|
|
end
|
|
|
|
# extract a list of modules to import from an expression
|
|
extract_imports!(imports, x) = imports
|
|
function extract_imports!(imports, ex::Expr)
|
|
if Meta.isexpr(ex, (:import, :using))
|
|
return push!(imports, ex.args[1])
|
|
elseif Meta.isexpr(ex, :let)
|
|
return extract_imports!(imports, ex.args[1])
|
|
elseif Meta.isexpr(ex, (:toplevel, :block))
|
|
for i in eachindex(ex.args)
|
|
extract_imports!(imports, ex.args[i])
|
|
end
|
|
end
|
|
return imports
|
|
end
|
|
extract_imports(x) = extract_imports!(Symbol[], x)
|
|
|
|
"""
|
|
@everywhere expr
|
|
|
|
Execute an expression under `Main` everywhere. Equivalent to calling
|
|
`eval(Main, expr)` on all processes. Errors on any of the processes are collected into a
|
|
`CompositeException` and thrown. For example :
|
|
|
|
@everywhere bar=1
|
|
|
|
will define `Main.bar` on all processes.
|
|
|
|
Unlike [`@spawn`](@ref) and [`@spawnat`](@ref),
|
|
`@everywhere` does not capture any local variables. Prefixing
|
|
`@everywhere` with [`@eval`](@ref) allows us to broadcast
|
|
local variables using interpolation :
|
|
|
|
foo = 1
|
|
@eval @everywhere bar=\$foo
|
|
|
|
The expression is evaluated under `Main` irrespective of where `@everywhere` is called from.
|
|
For example :
|
|
|
|
module FooBar
|
|
foo() = @everywhere bar()=myid()
|
|
end
|
|
FooBar.foo()
|
|
|
|
will result in `Main.bar` being defined on all processes and not `FooBar.bar`.
|
|
"""
|
|
macro everywhere(ex)
|
|
imps = [Expr(:import, m) for m in extract_imports(ex)]
|
|
quote
|
|
$(isempty(imps) ? nothing : Expr(:toplevel, imps...))
|
|
sync_begin()
|
|
for pid in workers()
|
|
async_run_thunk(()->remotecall_fetch(eval_ew_expr, pid, $(Expr(:quote,ex))))
|
|
yield() # ensure that the remotecall_fetch has been started
|
|
end
|
|
|
|
# execute locally last as we do not want local execution to block serialization
|
|
# of the request to remote nodes.
|
|
if nprocs() > 1
|
|
async_run_thunk(()->eval_ew_expr($(Expr(:quote,ex))))
|
|
end
|
|
|
|
sync_end()
|
|
end
|
|
end
|
|
|
|
eval_ew_expr(ex) = (eval(Main, ex); nothing)
|
|
|
|
# Statically split range [1,N] into equal sized chunks for np processors
|
|
function splitrange(N::Int, np::Int)
|
|
each = div(N,np)
|
|
extras = rem(N,np)
|
|
nchunks = each > 0 ? np : extras
|
|
chunks = Vector{UnitRange{Int}}(nchunks)
|
|
lo = 1
|
|
for i in 1:nchunks
|
|
hi = lo + each - 1
|
|
if extras > 0
|
|
hi += 1
|
|
extras -= 1
|
|
end
|
|
chunks[i] = lo:hi
|
|
lo = hi+1
|
|
end
|
|
return chunks
|
|
end
|
|
|
|
function preduce(reducer, f, R)
|
|
N = length(R)
|
|
chunks = splitrange(N, nworkers())
|
|
all_w = workers()[1:length(chunks)]
|
|
|
|
w_exec = Task[]
|
|
for (idx,pid) in enumerate(all_w)
|
|
t = Task(()->remotecall_fetch(f, pid, reducer, R, first(chunks[idx]), last(chunks[idx])))
|
|
schedule(t)
|
|
push!(w_exec, t)
|
|
end
|
|
reduce(reducer, [wait(t) for t in w_exec])
|
|
end
|
|
|
|
function pfor(f, R)
|
|
[@spawn f(R, first(c), last(c)) for c in splitrange(length(R), nworkers())]
|
|
end
|
|
|
|
function make_preduce_body(var, body)
|
|
quote
|
|
function (reducer, R, lo::Int, hi::Int)
|
|
$(esc(var)) = R[lo]
|
|
ac = $(esc(body))
|
|
if lo != hi
|
|
for $(esc(var)) in R[(lo+1):hi]
|
|
ac = reducer(ac, $(esc(body)))
|
|
end
|
|
end
|
|
ac
|
|
end
|
|
end
|
|
end
|
|
|
|
function make_pfor_body(var, body)
|
|
quote
|
|
function (R, lo::Int, hi::Int)
|
|
for $(esc(var)) in R[lo:hi]
|
|
$(esc(body))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
"""
|
|
@parallel
|
|
|
|
A parallel for loop of the form :
|
|
|
|
@parallel [reducer] for var = range
|
|
body
|
|
end
|
|
|
|
The specified range is partitioned and locally executed across all workers. In case an
|
|
optional reducer function is specified, `@parallel` performs local reductions on each worker
|
|
with a final reduction on the calling process.
|
|
|
|
Note that without a reducer function, `@parallel` executes asynchronously, i.e. it spawns
|
|
independent tasks on all available workers and returns immediately without waiting for
|
|
completion. To wait for completion, prefix the call with [`@sync`](@ref), like :
|
|
|
|
@sync @parallel for var = range
|
|
body
|
|
end
|
|
"""
|
|
macro parallel(args...)
|
|
na = length(args)
|
|
if na==1
|
|
loop = args[1]
|
|
elseif na==2
|
|
reducer = args[1]
|
|
loop = args[2]
|
|
else
|
|
throw(ArgumentError("wrong number of arguments to @parallel"))
|
|
end
|
|
if !isa(loop,Expr) || loop.head !== :for
|
|
error("malformed @parallel loop")
|
|
end
|
|
var = loop.args[1].args[1]
|
|
r = loop.args[1].args[2]
|
|
body = loop.args[2]
|
|
if na==1
|
|
thecall = :(pfor($(make_pfor_body(var, body)), $(esc(r))))
|
|
else
|
|
thecall = :(preduce($(esc(reducer)), $(make_preduce_body(var, body)), $(esc(r))))
|
|
end
|
|
thecall
|
|
end
|