Tôi ngạc nhiên bởi một tràn ngăn xếp trong chương trình dựa trên async của tôi. Tôi nghi ngờ vấn đề chính là với các chức năng sau đây, mà là vụ phải soạn hai tính async để thực hiện song song và chờ cho cả đến khi kết thúc:F # tràn bộ đệm async
let (<|>) (a: Async<unit>) (b: Async<unit>) =
async {
let! x = Async.StartChild a
let! y = Async.StartChild b
do! x
do! y
}
Với điều này được xác định, tôi đã sau chương trình mapReduce
mà cố gắng để khai thác song song trong cả hai phần map
và reduce
. Một cách không chính thức, ý tưởng là để kích hoạt các trình giảm tốc N
và N-1
sử dụng kênh được chia sẻ, chờ cho đến khi kết thúc và đọc kết quả từ kênh. Tôi đã có riêng Channel
thực hiện của tôi, ở đây thay thế bằng một ConcurrentBag
cho mã ngắn hơn (vấn đề ảnh hưởng đến cả hai):
let mapReduce (map : 'T1 -> Async<'T2>)
(reduce : 'T2 -> 'T2 -> Async<'T2>)
(input : seq<'T1>) : Async<'T2> =
let bag = System.Collections.Concurrent.ConcurrentBag()
let rec read() =
async {
match bag.TryTake() with
| true, value -> return value
| _ -> do! Async.Sleep 100
return! read()
}
let write x =
bag.Add x
async.Return()
let reducer =
async {
let! x = read()
let! y = read()
let! r = reduce x y
return bag.Add r
}
let work =
input
|> Seq.map (fun x -> async.Bind(map x, write))
|> Seq.reduce (fun m1 m2 -> m1 <|> m2 <|> reducer)
async {
do! work
return! read()
}
Bây giờ kiểm tra cơ bản sau đây bắt đầu ném StackOverflowException trên n = 10000:
let test n =
let map x = async.Return x
let reduce x y = async.Return (x + y)
mapReduce map reduce [0..n]
|> Async.RunSynchronously
EDIT: An thực hiện thay thế của <|>
combinator làm cho thử nghiệm thành công trên N = 10000:
let (<|>) (a: Async<unit>) (b: Async<unit>) =
Async.FromContinuations(fun (ok, _, _) ->
let count = ref 0
let ok() =
lock count (fun() ->
match !count with
| 0 -> incr count
| _ -> ok())
Async.Start <|
async {
do! a
return ok()
}
Async.Start <|
async {
do! b
return ok()
})
Đây là r Tôi thực sự ngạc nhiên vì đây là điều tôi cho là Async.StartChild
đang hoạt động. Bất kỳ suy nghĩ về giải pháp nào sẽ là tối ưu?
Sử dụng '1' Async.Sleep làm cho mã chậm hơn nhiều. Mặc dù điều đó có thể sẽ không hiển thị khi các hàm 'map' và' reduce' thực sự đã thực hiện một số công việc hữu ích, mất một thời gian. – svick
Thực ra, tôi không quan tâm đến sự phức tạp về thời gian/không gian ngay bây giờ - Tôi thực sự ngạc nhiên khi mã sử dụng ngăn xếp! Nếu cấu trúc nằm trên heap thì điều này sẽ tốt cho n = 10K. – t0yv0
@toyvo - Vâng, tràn ngăn xảy ra khi bắt đầu quy trình công việc, rõ ràng là sử dụng ngăn xếp. Chạy quy trình làm việc không cần ngăn xếp. Cách giải quyết 'Sleep 1' chỉ là để chứng minh rằng đây thực sự là vấn đề - bằng cách sử dụng' Seq.reduce' để xây dựng một cây nhị phân được giải mã nên giải quyết vấn đề mà không cần thêm phí. –