Julia Language
समानांतर प्रसंस्करण
खोज…
pmap
pmap
एक फ़ंक्शन लेता है (जो आप निर्दिष्ट करते हैं) और इसे सभी तत्वों में एक सरणी में लागू करता है। यह कार्य उपलब्ध श्रमिकों के बीच विभाजित है। pmap
फिर उस फ़ंक्शन के परिणामों को दूसरे सरणी में रखता है।
addprocs(3)
sqrts = pmap(sqrt, 1:10)
यदि आप कई तर्क देते हैं, तो आप pmap
को कई वैक्टर की आपूर्ति कर सकते हैं
dots = pmap(dot, 1:10, 11:20)
जैसा कि @parallel
साथ है, हालांकि, अगर pmap
को दिया गया pmap
बेस जूलिया में नहीं है (यानी यह यूज़र-डिफ़ाइंड या पैकेज में डिफाइन है) तो आपको यह सुनिश्चित करना होगा कि फंक्शन पहले सभी वर्कर्स के लिए उपलब्ध है:
@everywhere begin
function rand_det(n)
det(rand(n,n))
end
end
determinants = pmap(rand_det, 1:10)
यह भी देखें इस अतः क्यू एंड ए।
@parallel
@ समानांतर का उपयोग लूप को समानांतर बनाने के लिए किया जा सकता है, लूप के चरणों को विभिन्न श्रमिकों पर विभाजित किया जा सकता है। एक बहुत ही सरल उदाहरण के रूप में:
addprocs(3)
a = collect(1:10)
for idx = 1:10
println(a[idx])
end
थोड़ा और जटिल उदाहरण के लिए, विचार करें:
@time begin
@sync begin
@parallel for idx in 1:length(a)
sleep(a[idx])
end
end
end
27.023411 seconds (13.48 k allocations: 762.532 KB)
julia> sum(a)
55
इस प्रकार, हम देखते हैं कि अगर हमने @parallel
बिना इस लूप को @parallel
किया होता, तो इसे निष्पादित करने के लिए 27 के बजाय 55 सेकंड लगते।
हम @parallel
मैक्रो के लिए एक कमी ऑपरेटर भी आपूर्ति कर सकते हैं। मान लीजिए कि हमारे पास एक सरणी है, हम सरणी के प्रत्येक कॉलम को योग करना चाहते हैं और फिर इन योगों को एक दूसरे से गुणा करें:
A = rand(100,100);
@parallel (*) for idx = 1:size(A,1)
sum(A[:,idx])
end
अप्रत्याशित व्यवहार से बचने के लिए @parallel
का उपयोग करते समय कई महत्वपूर्ण बातों को ध्यान में रखना चाहिए।
पहला: यदि आप अपने फंक्शंस में ऐसे किसी फ़ंक्शंस का उपयोग करना चाहते हैं जो बेस जूलिया में नहीं हैं (जैसे या तो फ़ंक्शंस जो आप अपनी स्क्रिप्ट में परिभाषित करते हैं या जिसे आप पैकेजों से आयात करते हैं), तो आपको उन फ़ंक्शंस को श्रमिकों के लिए सुलभ बनाना होगा। इस प्रकार, उदाहरण के लिए, निम्नलिखित काम नहीं करेगा:
myprint(x) = println(x)
for idx = 1:10
myprint(a[idx])
end
इसके बजाय, हमें उपयोग करने की आवश्यकता होगी:
@everywhere begin
function myprint(x)
println(x)
end
end
@parallel for idx in 1:length(a)
myprint(a[idx])
end
दूसरा हालांकि प्रत्येक कार्यकर्ता नियंत्रक के दायरे में वस्तुओं का उपयोग करने में सक्षम होगा, वे उन्हें संशोधित करने में सक्षम नहीं होंगे। इस प्रकार
a = collect(1:10)
@parallel for idx = 1:length(a)
a[idx] += 1
end
julia> a'
1x10 Array{Int64,2}:
1 2 3 4 5 6 7 8 9 10
जबकि, अगर हमने लूप को अंजाम दिया होता @ @ समानांतर के रूप में, तो यह सफलतापूर्वक सरणी a
संशोधित करता।
इस पते पर, हम बजाय कर सकते हैं a
एक SharedArray
ताकि प्रत्येक कार्यकर्ता एक्सेस कर सकते हैं और इसे संशोधित प्रकार वस्तु:
a = convert(SharedArray{Float64,1}, collect(1:10))
@parallel for idx = 1:length(a)
a[idx] += 1
end
julia> a'
1x10 Array{Float64,2}:
2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0 11.0
@spawn और @spawnat
@spawn
और @spawnat
दो उपकरण हैं जो जूलिया श्रमिकों को काम सौंपने के लिए उपलब्ध @spawnat
हैं। यहाँ एक उदाहरण है:
julia> @spawnat 2 println("hello world")
RemoteRef{Channel{Any}}(2,1,3)
julia> From worker 2: hello world
ये दोनों मैक्रोज़ एक कार्यकर्ता प्रक्रिया पर एक अभिव्यक्ति का मूल्यांकन करेंगे। दोनों के बीच एकमात्र अंतर यह है कि @spawnat
आपको यह चुनने की अनुमति देता है कि @spawnat
सा कार्यकर्ता अभिव्यक्ति का मूल्यांकन करेगा (उदाहरण के लिए कार्यकर्ता 2 से ऊपर निर्दिष्ट किया गया है) जबकि @spawn
साथ एक कार्यकर्ता उपलब्धता के आधार पर स्वचालित रूप से चुना जाएगा।
उपरोक्त उदाहरण में, हमारे पास केवल कार्यकर्ता 2 था जो प्रिंटलाइन फ़ंक्शन को निष्पादित करता है। इसमें से लौटने या पुनः प्राप्त करने के लिए कोई दिलचस्पी नहीं थी। अक्सर, हालांकि, जो अभिव्यक्ति हमने कार्यकर्ता को भेजी है, वह हमें प्राप्त करने की इच्छा के साथ कुछ देगी। उपरोक्त उदाहरण में सूचना, जब हमने @spawnat
को @spawnat
, इससे पहले कि हम कार्यकर्ता 2 से प्रिंटआउट लें, हमने निम्नलिखित देखा:
RemoteRef{Channel{Any}}(2,1,3)
यह इंगित करता है कि @spawnat
मैक्रो RemoteRef
प्रकार की वस्तु लौटाएगा। बदले में इस ऑब्जेक्ट में हमारी अभिव्यक्ति से रिटर्न मान शामिल होंगे जो कार्यकर्ता को भेजे जाते हैं। यदि हम उन मानों को पुनः प्राप्त करना चाहते हैं, तो हम पहले RemoteRef
असाइन कर सकते हैं कि @spawnat
किसी ऑब्जेक्ट पर लौटता है और फिर, और fetch()
जो fetch()
RemoteRef
टाइप वस्तु पर संचालित होने वाले फ़ंक्शन का उपयोग करता है, एक मूल्यांकन से संग्रहीत परिणामों को प्राप्त करने के लिए। एक कर्मचारी।
julia> result = @spawnat 2 2 + 5
RemoteRef{Channel{Any}}(2,1,26)
julia> fetch(result)
7
@spawn
प्रभावी ढंग से उपयोग करने में सक्षम होने के कारण यह उन अभिव्यक्तियों के पीछे की प्रकृति को समझ रहा है जो इसे संचालित करती हैं। श्रमिकों को कमांड भेजने के लिए @spawn
का उपयोग करना सीधे टाइपिंग की तुलना में थोड़ा अधिक जटिल है कि आप क्या टाइप करेंगे यदि आप श्रमिकों में से एक पर "दुभाषिया" चला रहे थे या उन पर मूल रूप से कोड निष्पादित कर रहे थे। उदाहरण के लिए, मान लीजिए कि हमने किसी वर्कर पर वैरिएबल को मान देने के लिए @spawnat
का उपयोग करना चाहा है। हम कोशिश कर सकते हैं:
@spawnat 2 a = 5
RemoteRef{Channel{Any}}(2,1,2)
काम किया? ठीक है, मुद्रित करने के लिए होने कार्यकर्ता 2 कोशिश से देखते हैं a
।
julia> @spawnat 2 println(a)
RemoteRef{Channel{Any}}(2,1,4)
julia>
कुछ नहीं हुआ। क्यों? हम ऊपर के रूप में fetch()
का उपयोग करके इसकी अधिक जांच कर सकते हैं। fetch()
बहुत आसान हो सकता है क्योंकि यह न केवल सफल परिणाम प्राप्त करेगा, बल्कि त्रुटि संदेश भी देगा। इसके बिना, हम यह भी नहीं जानते होंगे कि कुछ गलत हो गया है।
julia> result = @spawnat 2 println(a)
RemoteRef{Channel{Any}}(2,1,5)
julia> fetch(result)
ERROR: On worker 2:
UndefVarError: a not defined
त्रुटि संदेश का कहना है कि a
कार्यकर्ता पर परिभाषित नहीं है 2. लेकिन ऐसा क्यों है? कारण यह है कि हमें अपने असाइनमेंट ऑपरेशन को एक अभिव्यक्ति में लपेटने की आवश्यकता है जो हम कार्यकर्ता का मूल्यांकन करने के लिए बताने के लिए @spawn
का उपयोग करते हैं। नीचे एक उदाहरण है, निम्नलिखित स्पष्टीकरण के साथ:
julia> @spawnat 2 eval(:(a = 2))
RemoteRef{Channel{Any}}(2,1,7)
julia> @spawnat 2 println(a)
RemoteRef{Channel{Any}}(2,1,8)
julia> From worker 2: 2
:()
सिंटैक्स वह है जो जूलिया अभिव्यक्ति को नामित करने के लिए उपयोग करता है। हम जूलिया में eval()
फ़ंक्शन का उपयोग करते हैं, जो एक अभिव्यक्ति का मूल्यांकन करता है, और हम @spawnat
मैक्रो का उपयोग करने के लिए निर्देश देते हैं कि अभिव्यक्ति का मूल्यांकन कार्यकर्ता 2 पर किया जाए।
हम भी उसी परिणाम को प्राप्त कर सकते हैं:
julia> @spawnat(2, eval(parse("c = 5")))
RemoteRef{Channel{Any}}(2,1,9)
julia> @spawnat 2 println(c)
RemoteRef{Channel{Any}}(2,1,10)
julia> From worker 2: 5
यह उदाहरण दो अतिरिक्त धारणाओं को प्रदर्शित करता है। सबसे पहले, हम देखते हैं कि हम स्ट्रिंग पर बुलाए गए parse()
फ़ंक्शन का उपयोग करके एक अभिव्यक्ति भी बना सकते हैं। दूसरे, हम देखते हैं कि हम उन स्थितियों में जहाँ यह हमारे वाक्यविन्यास को अधिक स्पष्ट और प्रबंधनीय बना सकते हैं, @spawnat
कॉल करते समय कोष्ठक का उपयोग कर सकते हैं।
जब @ अपरेल बनाम pmap का उपयोग करना है
जूलिया प्रलेखन सलाह देता है कि
pmap () उस मामले के लिए डिज़ाइन किया गया है जहां प्रत्येक फ़ंक्शन कॉल बड़ी मात्रा में काम करता है। इसके विपरीत, @ समानांतर उन स्थितियों को संभाल सकता है जहां प्रत्येक पुनरावृत्ति छोटी होती है, शायद केवल दो संख्याओं को जोड़ते हैं।
इसके अनेक कारण हैं। सबसे पहले, pmap
श्रमिकों पर रोजगार शुरू करने की लागत को अधिक pmap
। इस प्रकार, यदि नौकरियां बहुत छोटी हैं, तो ये स्टार्टअप लागत अक्षम हो सकती हैं। इसके विपरीत, हालांकि, pmap
श्रमिकों के बीच नौकरी आवंटित करने का "होशियार" काम करता है। विशेष रूप से, यह नौकरियों की एक कतार बनाता है और जब भी वह श्रमिक उपलब्ध हो जाता है, तो प्रत्येक श्रमिक को एक नई नौकरी भेजता है। इसके विपरीत @parallel
, श्रमिकों के बीच किए जाने वाले सभी कार्यों को @parallel
है जब इसे बुलाया जाता है। जैसे, यदि कुछ कार्यकर्ता दूसरों की तुलना में अपनी नौकरी में अधिक समय लेते हैं, तो आप एक ऐसी स्थिति के साथ समाप्त हो सकते हैं जहां आपके अधिकांश कार्यकर्ता समाप्त हो चुके हैं और निष्क्रिय हैं, जबकि कुछ अपनी नौकरी को समाप्त करने के लिए समय की एक विषम राशि के लिए सक्रिय रहते हैं। हालांकि, ऐसी स्थिति बहुत कम और सरल नौकरियों के साथ होने की संभावना कम है।
निम्नलिखित यह दर्शाता है: मान लें कि हमारे पास दो कार्यकर्ता हैं, जिनमें से एक धीमा है और दूसरा जो दोगुना है। आदर्श रूप में, हम तेज़ कामगार को दो बार देना चाहेंगे, जितना धीमे काम करने वाले को। (या, हम तेज़ और धीमी नौकरी कर सकते थे, लेकिन मूलधन वही है)। pmap
यह पूरा करेगा, लेकिन @parallel
नहीं होगा।
प्रत्येक परीक्षण के लिए, हम निम्नलिखित को प्रारंभिक करते हैं:
addprocs(2)
@everywhere begin
function parallel_func(idx)
workernum = myid() - 1
sleep(workernum)
println("job $idx")
end
end
अब, @parallel
परीक्षण के लिए, हम निम्नलिखित चलाते हैं:
@parallel for idx = 1:12
parallel_func(idx)
end
और वापस प्रिंट उत्पादन प्राप्त करें:
julia> From worker 2: job 1
From worker 3: job 7
From worker 2: job 2
From worker 2: job 3
From worker 3: job 8
From worker 2: job 4
From worker 2: job 5
From worker 3: job 9
From worker 2: job 6
From worker 3: job 10
From worker 3: job 11
From worker 3: job 12
यह लगभग मीठा है। श्रमिकों ने समान रूप से काम को "साझा" किया है। ध्यान दें कि प्रत्येक श्रमिक ने 6 कार्य पूरे किए हैं, भले ही कार्यकर्ता 2 श्रमिक के रूप में दोगुना हो। 3. यह स्पर्श हो सकता है, लेकिन यह अक्षम है।
pmap
परीक्षण के लिए, मैं निम्नलिखित चलाता हूं:
pmap(parallel_func, 1:12)
और उत्पादन प्राप्त करें:
From worker 2: job 1
From worker 3: job 2
From worker 2: job 3
From worker 2: job 5
From worker 3: job 4
From worker 2: job 6
From worker 2: job 8
From worker 3: job 7
From worker 2: job 9
From worker 2: job 11
From worker 3: job 10
From worker 2: job 12
अब, ध्यान दें कि कार्यकर्ता 2 ने 8 नौकरियां और कार्यकर्ता 3 ने प्रदर्शन किया है 4. यह उनकी गति के अनुपात में है, और हम इष्टतम दक्षता के लिए क्या चाहते हैं। pmap
एक हार्ड टास्क मास्टर है - प्रत्येक को उनकी क्षमता के अनुसार।
@async और @sync
के तहत प्रलेखन के अनुसार, ?@async
, " @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
मैक्रो को पूरी तरह से निष्पादित करने के लिए) कार्य की प्रतीक्षा किए बिना (इस मामले में, दो सेकंड के लिए सोते हुए) पूरा होने के लिए।
इसके विपरीत @sync
मैक्रो, "प्रतीक्षा करें जब तक @async
, @spawn
, @spawnat
और @parallel
सभी गतिशील रूप से संलग्न उपयोग पूर्ण नहीं हो जाते।" (के तहत प्रलेखन के अनुसार ?@sync
)। इस प्रकार, हम देखते हैं:
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)
यहाँ समस्या यह है कि लूप प्रत्येक remotecall_fetch()
ऑपरेशन को समाप्त करने के लिए इंतजार कर रहा है, यानी अगले remotecall_fetch()
ऑपरेशन को शुरू करने से पहले अपने काम को पूरा करने के लिए प्रत्येक प्रक्रिया के लिए (2 सेकंड के लिए सो रही है 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)
अब, यदि हम लूप के प्रत्येक चरण को एक अलग ऑपरेशन के रूप में गिनते हैं, तो हम देखते हैं कि @async
मैक्रो द्वारा पहले दो अलग-अलग ऑपरेशन हैं। मैक्रो इनमें से प्रत्येक को शुरू करने की अनुमति देता है, और प्रत्येक खत्म होने से पहले कोड जारी रखने के लिए (इस मामले में लूप के अगले चरण के लिए)। लेकिन, @sync
मैक्रो का उपयोग, जिसका दायरा पूरे लूप को @sync
करता है, का अर्थ है कि हम स्क्रिप्ट को उस लूप से आगे बढ़ने की अनुमति नहीं देंगे, जब तक कि @async
द्वारा पहले किए गए सभी ऑपरेशन पूरे नहीं हो जाते।
इन उदाहरणों को और अधिक स्पष्ट रूप से समझा जाना संभव है, ऊपर के उदाहरण को और अधिक देखते हुए कि यह कुछ संशोधनों के तहत कैसे बदलता है। उदाहरण के लिए, मान लें कि हमारे पास @async
बिना सिर्फ @sync
:
@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()
ऑपरेशन को पूरा करने से पहले ही हमारे लूप में जारी रखने की अनुमति देता है। लेकिन, बेहतर या बदतर के लिए, हमारे पास कोई भी @sync
मैक्रो नहीं है, ताकि कोड को इस लूप से पिछले तक रोका जा सके जब तक कि सभी remotecall_fetch()
ऑपरेशंस खत्म न हो जाएं।
फिर भी, प्रत्येक remotecall_fetch()
ऑपरेशन अभी भी समानांतर में चल रहा है, यहां तक कि एक बार जब हम चलते हैं। हम यह देख सकते हैं क्योंकि यदि हम दो सेकंड तक प्रतीक्षा करते हैं, तो सरणी a, जिसमें परिणाम होंगे, शामिल होंगे:
sleep(2)
julia> a
2-element Array{Any,1}:
nothing
nothing
("कुछ भी नहीं" तत्व नींद समारोह के परिणामों के एक सफल लाने का परिणाम है, जो किसी भी मूल्य को वापस नहीं करता है)
हम यह भी देख सकते हैं कि दो remotecall_fetch()
परिचालन अनिवार्य रूप से एक ही समय में शुरू होते हैं क्योंकि print
कमांड जो उन्हें पूर्ववर्ती करते हैं वे भी तेजी से उत्तराधिकार में निष्पादित होते हैं (इन कमांड से आउटपुट यहां नहीं दिखाया गया है)। अगले उदाहरण के साथ इसका विरोध करें जहां print
कमांड एक दूसरे से 2 सेकंड के अंतराल पर निष्पादित होते हैं:
यदि हम पूरे लूप पर (केवल इसके आंतरिक चरण के बजाय) पर @async
मैक्रो @async
, तो फिर से remotecall_fetch()
संचालन की प्रतीक्षा किए बिना हमारी स्क्रिप्ट तुरंत जारी रहेगी। अब, हालांकि, हम केवल स्क्रिप्ट को लूप को एक पूरे के रूप में जारी रखने की अनुमति देते हैं। हम पिछले एक समाप्त होने से पहले लूप के प्रत्येक व्यक्तिगत चरण को शुरू करने की अनुमति नहीं देते हैं। जैसे, उपरोक्त उदाहरण के विपरीत, लूप के बाद स्क्रिप्ट के आगे #undef
दो सेकंड बाद, results
सरणी में अभी भी #undef
रूप में एक तत्व है जो दर्शाता है कि दूसरा remotecall_fetch()
ऑपरेशन अभी भी पूरा नहीं हुआ है।
@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()
क्रमिक रूप से (एक साथ बजाय 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
मैक्रो के दायरे में अधिक जटिल ऑपरेशन करना संभव है। प्रलेखन @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 सेकंड का समय लगा, यह दर्शाता है कि दो कार्यों को समानांतर में चलाया गया था और यह कि स्क्रिप्ट प्रत्येक को आगे बढ़ने से पहले अपने कार्यों को पूरा करने की प्रतीक्षा कर रही है। हालाँकि, इस उदाहरण का मूल्यांकन बहुत कम है। कारण यह है कि @sync
के उद्देश्यों के लिए एक बार यह काम करने के लिए कर्मचारी को भेजने के बाद remotecall()
ऑपरेशन "समाप्त" हो गया है। (ध्यान दें कि परिणामी सरणी, a, यहाँ, बस में RemoteRef
ऑब्जेक्ट प्रकार शामिल हैं, जो केवल यह संकेत देते हैं कि किसी विशेष प्रक्रिया के साथ कुछ चल रहा है जो कि भविष्य में किसी बिंदु पर सिद्धांत रूप में लाया जा सकता है)। इसके विपरीत, remotecall_fetch()
ऑपरेशन में केवल "समाप्त" होता है जब उसे कार्यकर्ता से संदेश मिलता है कि उसका कार्य पूरा हो गया है।
इस प्रकार, यदि आप यह सुनिश्चित करने के तरीकों की तलाश कर रहे हैं कि आपकी स्क्रिप्ट में आगे बढ़ने से पहले श्रमिकों के साथ कुछ संचालन पूरा हो गया है (उदाहरण के लिए इस पोस्ट में चर्चा की गई है) तो यह ध्यान से सोचना आवश्यक है कि "पूर्ण" के रूप में क्या मायने रखता है और आप कैसे हैं उपाय करें और फिर उसे अपनी लिपि में परिचालित करें।
वर्कर्स को जोड़ना
जब आप पहली बार जूलिया शुरू करते हैं, तो डिफ़ॉल्ट रूप से, केवल एक ही प्रक्रिया चल रही होगी और काम देने के लिए उपलब्ध होगी। आप इसका उपयोग करके सत्यापित कर सकते हैं:
julia> nprocs()
1
समानांतर प्रसंस्करण का लाभ उठाने के लिए, आपको पहले अतिरिक्त श्रमिकों को जोड़ना होगा जो तब काम करने के लिए उपलब्ध होंगे जो आप उन्हें असाइन करते हैं। आप इसका उपयोग अपनी स्क्रिप्ट (या दुभाषिया से) के भीतर कर सकते हैं: addprocs(n)
जहां n
उन प्रक्रियाओं की संख्या है जिनका आप उपयोग करना चाहते हैं।
वैकल्पिक रूप से, जब आप कमांड लाइन से जूलिया का उपयोग शुरू करते हैं तो आप प्रक्रियाएँ जोड़ सकते हैं:
$ julia -p n
जहाँ n
कितनी अतिरिक्त प्रक्रियाएँ आप जोड़ना चाहते हैं इस प्रकार, अगर हम जूलिया के साथ शुरू करते हैं
$ julia -p 2
जूलिया शुरू होने पर हमें मिलेगा:
julia> nprocs()
3