Harika bir soru.
Fibonacci işlevinin bu çok iş parçacıklı uygulaması , tek iş parçacıklı sürümden daha hızlı değildir . Bu işlev, blog yazısında sadece yeni iş parçacığı özelliklerinin nasıl çalıştığına dair bir oyuncak örneği olarak gösterildi, farklı işlevlerde çok sayıda iş parçacığının ortaya çıkmasına izin verdiğini ve zamanlayıcının en uygun iş yükünü bulacağını vurguladı.
Sorun şu ki @spawn
, önemsiz bir ek yüke sahip 1µs
, bu nedenle daha az süren bir görev yapmak için bir iş parçacığı ortaya çıkarırsanız 1µs
, muhtemelen performansınıza zarar vermiş olursunuz. Özyinelemeli tanımının fib(n)
sıralamanın üstel zaman karmaşıklığı vardır 1.6180^n
[1], dolayısıyla aradığınızda fib(43)
, sipariş 1.6180^43
iplikleri ortaya çıkar. Her birinin 1µs
yumurtlaması gerekiyorsa, sadece gerekli iş parçacıklarının ortaya çıkması ve zamanlanması yaklaşık 16 dakika sürer ve bu, gerçek hesaplamaları yapmak için gereken süreyi hesaba katmaz ve daha fazla zaman.
Bir hesaplamanın her adımı için bir iş parçacığı oluşturduğunuz gibi şeyler yalnızca hesaplamanın her adımı @spawn
yüke göre uzun zaman alıyorsa mantıklıdır .
Ek yükü azaltmaya yönelik bir çalışma olduğunu unutmayın @spawn
, ancak çok çekirdekli silikon çiplerin fiziği ile yukarıdaki fib
uygulama için yeterince hızlı olabileceğinden şüpheliyim .
İş parçacığı fib
işlevini gerçekten yararlı olacak şekilde nasıl değiştirebileceğimizi merak ediyorsanız , yapılacak en kolay şey, yalnızca çalıştırılmasından fib
çok daha uzun süreceğini düşünürsek bir iş parçacığı 1µs
oluşturmaktır. Makinemde (16 fiziksel çekirdek üzerinde çalışıyor) alıyorum
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)
yani bir ipliğin yumurtlama maliyeti üzerinde büyüklükte iyi bir iki sipariş thats. Kullanmak için iyi bir kesme gibi görünüyor:
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
şimdi, BenchmarkTools.jl [2] ile uygun kıyaslama metodolojisini takip edersem
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 yorumlarda soruyor: Bu, 16 çekirdeği kullanarak 2 hızlanma faktörü gibi görünüyor. 16 faktöre daha yakın bir şey elde etmek mümkün müdür?
Evet öyle. Yukarıdaki işlevle ilgili sorun, işlev gövdesinin, F
birçok koşullu, işlev / iş parçacığı yumurtlama ve tüm bunlardan daha büyük olmasıdır. Sizi karşılaştırmaya davet ediyorum @code_llvm F(10)
@code_llvm fib(10)
. Bu fib
, Julia'nın optimizasyonunun çok daha zor olduğu anlamına gelir . Bu ekstra yük, küçük n
vakalar için bir dünya yaratır .
julia> @btime F(20);
28.844 μs (0 allocations: 0 bytes)
julia> @btime fib(20);
242.208 μs (20 allocations: 320 bytes)
Oh hayır! hiçbir zaman dokunulmayan ekstra kodlar n < 23
bizi bir büyüklük sırasına göre yavaşlatıyor! Yine de kolay bir düzeltme var: ne zaman n < 23
, geri çekilmeyin fib
, bunun yerine tek dişliyi çağırın 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
bu da pek çok iş parçacığı için beklediğimiz şeye daha yakın bir sonuç verir.
[1] https://www.geeksforgeeks.org/time-complexity-recursive-fibonacci-program/
[2] BenchmarkTools.jl @btime
dosyasındaki BenchmarkTools makrosu, derleme süresini ve ortalama sonuçları atlayarak işlevleri birden çok kez çalıştıracaktır.