जूलिया में फिबोनाची अनुक्रम के साथ बहु-थ्रेडेड समानांतरवाद प्रदर्शन समस्या (1.3)


14

मैं Julia 1.3निम्नलिखित हार्डवेयर के साथ बहु समारोह की कोशिश कर रहा हूँ :

Model Name: MacBook Pro
Processor Name: Intel Core i7
Processor Speed:    2.8 GHz
Number of Processors:   1
Total Number of Cores:  4
L2 Cache (per Core):    256 KB
L3 Cache:   6 MB
Hyper-Threading Technology: Enabled
Memory: 16 GB

निम्नलिखित स्क्रिप्ट चलाते समय:

function F(n)
if n < 2
    return n
    else
        return F(n-1)+F(n-2)
    end
end
@time F(43)

यह मुझे निम्न आउटपुट देता है

2.229305 seconds (2.00 k allocations: 103.924 KiB)
433494437

हालाँकि जब मल्टीथ्रेडिंग के बारे में जूलिया पेज से कॉपी किया गया निम्न कोड चल रहा है

import Base.Threads.@spawn

function fib(n::Int)
    if n < 2
        return n
    end
    t = @spawn fib(n - 2)
    return fib(n - 1) + fetch(t)
end

fib(43)

क्या होता है कि RAM / CPU का उपयोग 3.2GB / 6% से 15GB / 25% तक किसी भी आउटपुट के बिना (कम से कम 1 मिनट के लिए होता है, जिसके बाद मैंने जूलिया सत्र को मारने का फैसला किया)

मैं क्या गलत कर रहा हूं?

जवाबों:


19

बड़ा सवाल है।

फाइबोनैचि फ़ंक्शन का यह बहुस्तरीय कार्यान्वयन एकल थ्रेडेड संस्करण की तुलना में तेज़ नहीं है । उस फ़ंक्शन को केवल ब्लॉग पोस्ट में एक खिलौना उदाहरण के रूप में दिखाया गया था कि नई थ्रेडिंग क्षमताओं कैसे काम करती हैं, इस पर प्रकाश डालते हुए कि यह विभिन्न कार्यों में कई थ्रेड्स को थूकने की अनुमति देता है और अनुसूचक एक इष्टतम कार्यभार का पता लगाएगा।

समस्या यह है कि @spawnचारों ओर एक गैर-तुच्छ उपरि है 1µs, इसलिए यदि आप किसी कार्य को करने के लिए थ्रेड स्पॉन करते हैं 1µs, जो आपको कम लगता है , तो आपने संभवतः अपने प्रदर्शन को चोट पहुंचाई है। पुनरावर्ती परिभाषा में fib(n)आदेश की घातीय समय जटिलता है 1.6180^n[1], इसलिए जब आप कॉल करते हैं, तो आप fib(43)ऑर्डर 1.6180^43थ्रेड्स के कुछ स्पॉन करते हैं । यदि प्रत्येक 1µsको स्पॉन लेना है, तो आवश्यक थ्रेड्स को स्पॉन और शेड्यूल करने में लगभग 16 मिनट लगेंगे, और यह वास्तविक कम्प्यूटेशन करने के लिए समय भी नहीं लेता है और फिर से विलय / सिंक थ्रेड्स लेता है। ज्यादा समय।

इस तरह की चीजें जहां आप गणना के प्रत्येक चरण के लिए एक धागा बनाते हैं, केवल तभी समझ में आता है यदि गणना का प्रत्येक चरण @spawnओवरहेड की तुलना में लंबा समय लेता है ।

ध्यान दें कि ओवरहेड को कम करने में काम चल रहा है @spawn, लेकिन मल्टीकोर सिलिकॉन चिप्स की बहुत भौतिकी से मुझे संदेह है कि यह उपरोक्त fibकार्यान्वयन के लिए कभी भी पर्याप्त तेज़ हो सकता है ।


यदि आप इस बारे में उत्सुक हैं कि हम थ्रेडेड fibफ़ंक्शन को वास्तव में लाभकारी बनाने के लिए कैसे संशोधित कर सकते हैं, तो सबसे आसान काम केवल एक fibधागा स्पॉन होगा यदि हमें लगता है कि इसे 1µsचलाने में काफी अधिक समय लगेगा । मेरी मशीन पर (16 भौतिक कोर पर चल रहा है), मुझे मिलता है

function F(n)
    if n < 2
        return n
    else
        return F(n-1)+F(n-2)
    end
end


julia> @btime F(23);
  122.920 μs (0 allocations: 0 bytes)

तो एक धागा spawning की लागत पर परिमाण का एक अच्छा दो आदेश thats। यह उपयोग करने के लिए एक अच्छा कटऑफ जैसा लगता है:

function fib(n::Int)
    if n < 2
        return n
    elseif n > 23
        t = @spawn fib(n - 2)
        return fib(n - 1) + fetch(t)
    else
        return fib(n-1) + fib(n-2)
    end
end

अब, अगर मैं बेंचमार्कटूलसीजेएल [2] के साथ उचित बेंचमार्क पद्धति का पालन करता हूं

julia> using BenchmarkTools

julia> @btime fib(43)
  971.842 ms (1496518 allocations: 33.64 MiB)
433494437

julia> @btime F(43)
  1.866 s (0 allocations: 0 bytes)
433494437

@Anush टिप्पणियों में पूछता है: यह 16 कोर का उपयोग करके 2 गति का एक कारक है जो ऐसा लगता है। क्या 16 गति के एक कारक के करीब कुछ प्राप्त करना संभव है?

हाँ यही है। उपरोक्त फ़ंक्शन के साथ समस्या यह है कि फ़ंक्शन बॉडी की तुलना में बड़ा है F, बहुत सारे सशर्त, फ़ंक्शन / थ्रेड स्पैनिंग और सभी। मैं आपको तुलना करने के लिए आमंत्रित करता हूं @code_llvm F(10) @code_llvm fib(10)। इसका मतलब है कि fibजूलिया के लिए अनुकूलन करना बहुत कठिन है। यह अतिरिक्त ओवरहेड यह छोटे nमामलों के लिए अंतर की दुनिया बनाता है ।

julia> @btime F(20);
  28.844 μs (0 allocations: 0 bytes)

julia> @btime fib(20);
  242.208 μs (20 allocations: 320 bytes)

अरे नहीं! वह सब अतिरिक्त कोड जो कभी नहीं छूता है n < 23वह हमें परिमाण के एक क्रम से धीमा कर रहा है! हालांकि एक आसान तय है: जब n < 23, fibएक ही पिरोया कॉल करने के लिए , नीचे पुन : उठना नहीं है F

function fib(n::Int)
    if n > 23
       t = @spawn fib(n - 2)
       return fib(n - 1) + fetch(t)
    else
       return F(n)
    end
end

julia> @btime fib(43)
  138.876 ms (185594 allocations: 13.64 MiB)
433494437

जो इतने सारे थ्रेड्स के लिए हम क्या चाहते हैं के करीब एक परिणाम देता है।

[१] https://www.geeksforgeeks.org/time-complexity-recursive-fibnote-program/

[२] बेंचमार्कटूल @btimeसे बेंचमार्कटूल मैक्रो कई बार फंक्शन को चलाएगा, जो संकलन समय और औसत परिणाम को छोड़ देगा।


1
यह 2 कोर का ऐसा कारक है जो 16 कोर का उपयोग करता है। क्या 16 गति के एक कारक के करीब कुछ प्राप्त करना संभव है?
अनुष्का

बड़े बेस केस का इस्तेमाल करें। BTW, यह कैसे प्रभावी रूप से मल्टीफ़्रेड प्रोग्राम जैसे एफएफटीडब्ल्यू जैसे हुड के तहत काम करता है!
क्रिस राकाउकास

बड़ा आधार मामला मदद नहीं करता है। चाल यह है कि fibजूलिया से अनुकूलन करने के लिए कठिन है F, इसलिए हम केवल Fइसके बजाय का उपयोग fibकरते हैं n< 23। मैंने अपने उत्तर को अधिक स्पष्ट व्याख्या और उदाहरण के साथ संपादित किया।
मेसन

यह अजीब है, मुझे वास्तव में ब्लॉग पोस्ट उदाहरण का उपयोग करके बेहतर परिणाम मिले ...
tpdsantos

@tpdsantos Threads.nthreads()आपके लिए आउटपुट क्या है ? मुझे संदेह है कि आप जूलिया को केवल एक धागे के साथ चला रहे होंगे।
मेसन

0

@Anush

मैन्युअल रूप से संस्मरण और मल्टीथ्रेडिंग का उपयोग करने के एक उदाहरण के रूप में

_fib(::Val{1}, _,  _) = 1
_fib(::Val{2}, _, _) = 1

import Base.Threads.@spawn
_fib(x::Val{n}, d = zeros(Int, n), channel = Channel{Bool}(1)) where n = begin
  # lock the channel
  put!(channel, true)
  if d[n] != 0
    res = d[n]
    take!(channel)
  else
    take!(channel) # unlock channel so I can compute stuff
    #t = @spawn _fib(Val(n-2), d, channel)
    t1 =  _fib(Val(n-2), d, channel)
    t2 =  _fib(Val(n-1), d, channel)
    res = fetch(t1) + fetch(t2)

    put!(channel, true) # lock channel
    d[n] = res
    take!(channel) # unlock channel
  end
  return res
end

fib(n) = _fib(Val(n), zeros(Int, n), Channel{Bool}(1))


fib(1)
fib(2)
fib(3)
fib(4)
@time fib(43)


using BenchmarkTools
@benchmark fib(43)

लेकिन स्पीड अप मेमोइज़ेशन से आया और इतना मल्टीथ्रेडिंग नहीं। यहां सबक यह है कि हमें मल्टीथ्रेडिंग से पहले बेहतर एल्गोरिदम के बारे में सोचना चाहिए।


यह सवाल कभी भी फाइबोनैचि संख्याओं की तेजी से गणना करने के बारे में नहीं था। मुद्दा यह था कि 'इस भोले-भाले कार्यान्वयन में सुधार क्यों नहीं किया जा रहा है?'
मेसन

मेरे लिए, अगला तार्किक प्रश्न है: इसे कैसे तेज किया जाए। इसलिए इसे पढ़ने वाला कोई व्यक्ति मेरा समाधान देख सकता है और उससे सीख सकता है, शायद।
xiaodai
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.