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