জুলিয়ায় ফিবোনাচি সিকোয়েন্সের সাথে মাল্টি-থ্রেডড সমান্তরালতা পারফরম্যান্স সমস্যা (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)

যা ঘটে তা হ'ল র‍্যাম / সিপিইউ ব্যবহার কোনও আউটপুট ছাড়াই ৩.২ জিবি / GB% থেকে ১৫ গিগাবাইট / ২৫% পর্যন্ত লাফিয়ে যায় (কমপক্ষে 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চলার চেয়ে বেশি সময় নেয় take আমার মেশিনে (১ physical টি শারীরিক কোরে চলছে), আমি পেয়েছি

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)

সুতরাং একটি থ্রেড তৈরির ব্যয়ের চেয়ে বিশাল দুটি অর্ডারের প্রশংসা করে। এটি ব্যবহার করার জন্য একটি ভাল কাট অফের মতো বলে মনে হচ্ছে:

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

@ আনুশ মন্তব্যগুলিতে জিজ্ঞাসা করেছেন: এটি দেখে মনে হচ্ছে 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

যা এতগুলি থ্রেডের জন্য আমরা কী প্রত্যাশা করি তার কাছাকাছি ফলাফল দেয়।

[1] https://www.geeksforgeeks.org/time-complexity-recursive-fibonacci-program/

[২] বেঞ্চমার্কটুলস.জেএল @btimeথেকে বেনমার্কটুলস ম্যাক্রো একাধিকবার ফাংশন পরিচালনা করবে, সংকলনের সময় এবং গড় ফলাফলগুলি এড়িয়ে চলে।


1
দেখে মনে হচ্ছে এটি 16 গিগাবাইট ব্যবহার করে 2 গতি বাড়ানোর একটি কারণ factor 16 গতির গতির একটি ফ্যাক্টরের কাছাকাছি কিছু পাওয়া সম্ভব?
আনুশ

একটি বৃহত্তর বেস কেস ব্যবহার করুন। বিটিডাব্লু, এফএফটিডাব্লির মতো মাল্টিথ্রেডেড প্রোগ্রামগুলি কার্যকরভাবে হুডের নিচেও কাজ করে!
ক্রিস রাকাকাকাস

বৃহত্তর বেস কেস সাহায্য করে না। কৌতুক করে 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
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.