[email protected]
のドキュメントによれば、 "@async
は、式をタスクにラップします。"これが意味することは、その範囲内にあるものについては、ジュリアはこのタスクを実行し始めますが、タスクが完了するのを待たずにスクリプトの次のものに進むことです。したがって、例えば、マクロなしであなたが得る:
julia> @time sleep(2)
2.005766 seconds (13 allocations: 624 bytes)
をしかし、マクロで、あなたが得る:
julia> @time @async sleep(2)
0.000021 seconds (7 allocations: 657 bytes)
Task (waiting) @0x0000000112a65ba0
julia>
ジュリアのためのスクリプトを続行することができます(と完全に実行するマクロ@time
)タスク(この場合は2秒間スリープ状態)が完了するのを待たずに
@sync
マクロは、対照的に、「@async
、@spawn
、@spawnat
と@parallel
のすべての動的に囲まれた用途が完了するまでお待ちください。」になります([email protected]
の文書による)。したがって、我々は参照:
julia> @time @sync @async sleep(2)
2.002899 seconds (47 allocations: 2.986 KB)
Task (done) @0x0000000112bd2e00
この単純な例では、次に、@async
と一緒@sync
の単一インスタンスを含むことにも意味がありません。しかし、@sync
が便利な場合は、@async
を複数の操作に適用して、それぞれの操作が完了するのを待たずにすぐにすべての操作を一度に開始することができます。
たとえば、複数のワーカーがいるとします。それぞれのタスクを同時に開始してから、それらのタスクから結果を取得したいとします。最初の(しかし間違った)試みは次のようになります。
addprocs(2)
@time begin
a = cell(nworkers())
for (idx, pid) in enumerate(workers())
a[idx] = remotecall_fetch(pid, sleep, 2)
end
end
## 4.011576 seconds (177 allocations: 9.734 KB)
ここでの問題は、ループが2秒間スリープ(この場合、その作業を完了するために、各プロセスのために、すなわち、終了する各remotecall_fetch()操作のために待機していることです)、次のremotecall_fetch()操作を開始します。現実的な状況では、プロセスの作業(つまりスリープ)が同時に行われていないため、並列処理の利点は得られません。
我々は@async
と@sync
マクロの組み合わせを使用することによって、しかし、これを修正することができます。私たちは別の操作として、ループの各ステップを数える場合
@time begin
a = cell(nworkers())
@sync for (idx, pid) in enumerate(workers())
@async a[idx] = remotecall_fetch(pid, sleep, 2)
end
end
## 2.009416 seconds (274 allocations: 25.592 KB)
は今、私たちは2があることがわかり別の操作の前には@async
マクロがあります。マクロは、これらのそれぞれが起動し、コードが終了する前に(この場合はループの次のステップに)進むことができます。しかし、ループ全体をスコープとする@sync
マクロを使用すると、@async
より前のすべての操作が完了するまでスクリプトがそのループを通過することは許可されません。
これらのマクロの動作をさらに明確に理解するために、上の例をさらに微調整して、特定の変更の下でどのように変化するかを確認することができます。たとえば、私たちは@sync
なし@async
があるとします。
ここ
@time begin
a = cell(nworkers())
for (idx, pid) in enumerate(workers())
println("sending work to $pid")
@async a[idx] = remotecall_fetch(pid, sleep, 2)
end
end
## 0.001429 seconds (27 allocations: 2.234 KB)
を、@async
マクロは、私たちは、各remotecall_fetch()操作の実行が終了する前であっても、当社のループで継続することができます。しかし、すべてのremotecall_fetch()操作が終了するまで、コードがこのループを過ぎないようにするために、より良いか悪いかにかかわらず、@sync
マクロはありません。
しかし、remotecall_fetch()の各操作は、いったん実行しても、まだ並行して実行されています。私たちは2秒間待っていればいるため、その配列は、結果を含む、含まれていることがわかります。
sleep(2)
julia> a
2-element Array{Any,1}:
nothing
nothing
(「何もない」要素が成功の結果である睡眠の結果のフェッチファンクションは、値を返さない)
2つのremotecall_fetch()操作は、それらの前にある印刷コマンドも高速で連続して実行されるため、本質的に同時に開始することがわかります)。これを次の例と比較してください。
@async
マクロをループの内側に配置するだけで、スクリプトはremotecall_fetch()操作が完了するのを待たずにすぐに続行してください。しかし、今度は、スクリプトがループ全体を越えて進むことを許可します。前のループが完了する前にループの個々のステップを開始することはできません。前述の例とは異なり、スクリプトがループの後に続く2秒後に、結果配列には2つ目のremotecall_fetch()操作がまだ完了していないことを示す#undefという要素が1つあります。
@time begin
a = cell(nworkers())
@async for (idx, pid) in enumerate(workers())
println("sending work to $pid")
a[idx] = remotecall_fetch(pid, sleep, 2)
end
end
# 0.001279 seconds (328 allocations: 21.354 KB)
# Task (waiting) @0x0000000115ec9120
## This also allows us to continue to
sleep(2)
a
2-element Array{Any,1}:
nothing
#undef
そして、ない、我々は右隣同士に@sync
と@async
を置けば、驚くほど、私たちはそれぞれremotecall_fetchは、()(同時にではなく)を順次実行することを得るが、私たちは、それぞれまでのコードで継続していません終わりました。言い換えれば、これは、私は信じて、我々はsleep(2)
が、内部のより複雑な操作を持つことが可能であることも@sync @async sleep(2)
@time begin
a = cell(nworkers())
@sync @async for (idx, pid) in enumerate(workers())
a[idx] = remotecall_fetch(pid, sleep, 2)
end
end
# 4.019500 seconds (4.20 k allocations: 216.964 KB)
# Task (done) @0x0000000115e52a10
ノートに、本質的に同じ動作と同じように、場所にもないマクロがあった場合の、本質的に相当します@async
マクロのスコープdocumentationは、@async
の範囲内のループ全体を含む例を示しています。
更新:リコール同期マクロのヘルプは、それは「@async
、@spawn
、@spawnat
と@parallel
のすべての動的に囲まれた用途が完了するまでお待ちください。」だろうと述べていること「完全」とみなされる目的のために、@sync
と@async
マクロの範囲内でタスクを定義する方法が重要です。上記の例のいずれかにわずかな変形である、以下の実施例考える:
@time begin
a = cell(nworkers())
@sync for (idx, pid) in enumerate(workers())
@async a[idx] = remotecall(pid, sleep, 2)
end
end
## 0.172479 seconds (93.42 k allocations: 3.900 MB)
julia> a
2-element Array{Any,1}:
RemoteRef{Channel{Any}}(2,1,3)
RemoteRef{Channel{Any}}(3,1,4)
以前の例では、2つのタスクを並列に実行されたことを示し、実行するために約2秒を要し、スクリプトが待っていることそれぞれの機能の実行を完了してから先に進んでください。しかし、この例では、時間評価がずっと短くなっています。その理由は、@sync
の目的のために、remotecall()操作は作業者に作業を送った後に「終了」しているためです。 (結果として得られる配列aにはRemoteRefオブジェクト型が含まれているにすぎないことに注意してください。これは、将来、ある時点でフェッチされる可能性のある特定のプロセスで起こっていることを示しています)。対照的に、remotecall_fetch()操作は、作業が完了したというメッセージをワーカーから取得した場合にのみ「終了」します。
このように、スクリプト内を移動する前に作業者が特定の操作を完了するための方法を探している場合(例:Waiting for a task to be completed on remote processor in Julia)、何が重要なのかを慎重に考える必要があります。あなたのスクリプトでそれを測定し、それをどのように操作するのかを説明します。
この投稿は、この投稿の@FelipeLemaによる有益な回答とディスカッションからインスピレーションを受けました:http://stackoverflow.com/questions/32143159/waiting-for-a-task-to-be-completed-on-remote-プロセッサインジュリア/ 32148849#32148849 –
素敵な答え! – StefanKarpinski