Câu hỏi tuyệt vời.
Việc triển khai đa luồng này của hàm Fibonacci không nhanh hơn phiên bản luồng đơn. Chức năng đó chỉ được hiển thị trong bài đăng trên blog như một ví dụ về đồ chơi về cách các khả năng phân luồng mới hoạt động, làm nổi bật rằng nó cho phép sinh ra nhiều luồng trong các chức năng khác nhau và bộ lập lịch sẽ tìm ra một khối lượng công việc tối ưu.
Vấn đề là @spawn
có một chi phí không hề nhỏ xung quanh 1µs
, vì vậy nếu bạn sinh ra một chủ đề để thực hiện một nhiệm vụ ít hơn 1µs
, bạn có thể làm tổn thương hiệu suất của bạn. Định nghĩa đệ quy fib(n)
có độ phức tạp theo thời gian theo cấp số nhân của đơn hàng 1.6180^n
[1], vì vậy khi bạn gọi fib(43)
, bạn sinh ra một cái gì đó của các chuỗi đơn hàng 1.6180^43
. Nếu mỗi người 1µs
sinh sản, sẽ mất khoảng 16 phút để sinh sản và lên lịch cho các chủ đề cần thiết và thậm chí không tính đến thời gian để thực hiện các tính toán thực tế và hợp nhất lại / đồng bộ hóa các chủ đề mà thậm chí mất thêm thời gian.
Những thứ như thế này khi bạn sinh ra một luồng cho mỗi bước tính toán chỉ có ý nghĩa nếu mỗi bước tính toán mất nhiều thời gian so với @spawn
chi phí chung.
Lưu ý rằng có công việc đang giảm bớt chi phí hoạt động @spawn
, nhưng bằng chính tính vật lý của chip silicon đa lõi, tôi nghi ngờ nó có thể đủ nhanh để fib
thực hiện ở trên .
Nếu bạn tò mò về cách chúng ta có thể sửa đổi fib
hàm luồng để thực sự có ích, thì cách dễ nhất là chỉ tạo ra một fib
luồng nếu chúng ta nghĩ rằng nó sẽ mất nhiều thời gian hơn 1µs
để chạy. Trên máy của tôi (chạy trên 16 lõi vật lý), tôi nhận được
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)
vì vậy đó là một đơn đặt hàng lớn hai lần so với chi phí sinh ra một chủ đề. Điều đó có vẻ như là một điểm cắt tốt để sử dụng:
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
bây giờ, nếu tôi làm theo phương pháp điểm chuẩn thích hợp với BenchmarkTools.jl [2] tôi thấy
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 hỏi trong các ý kiến: Đây là một yếu tố tăng tốc độ bằng cách sử dụng 16 lõi. Có thể có được một cái gì đó gần hơn với một yếu tố 16 tăng tốc?
Vâng, đúng vậy. Vấn đề với chức năng trên là cơ thể chức năng lớn hơn cơ thể F
, với rất nhiều điều kiện, chức năng / luồng sinh sản và tất cả điều đó. Tôi mời bạn so sánh @code_llvm F(10)
@code_llvm fib(10)
. Điều này có nghĩa fib
là khó khăn hơn nhiều cho julia để tối ưu hóa. Chi phí hoạt động thêm này tạo nên một thế giới khác biệt cho những n
trường hợp nhỏ .
julia> @btime F(20);
28.844 μs (0 allocations: 0 bytes)
julia> @btime fib(20);
242.208 μs (20 allocations: 320 bytes)
Ôi không! tất cả các mã bổ sung mà không bao giờ được chạm vào n < 23
đang làm chúng ta chậm lại bởi một thứ tự cường độ! Có một cách khắc phục dễ dàng: khi nào n < 23
, đừng lặp lại fib
, thay vào đó hãy gọi một luồng 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
mang lại kết quả gần hơn với những gì chúng ta mong đợi cho rất nhiều chủ đề.
[1] https://www.geekforgeek.org/time-complexity-recursive-fiborie-program/
[2] @btime
Macro BenchmarkTools từ BenchmarkTools.jl sẽ chạy các chức năng nhiều lần, bỏ qua thời gian biên dịch và kết quả trung bình.