Async.Parallel
почти наверняка прямо здесь.Не уверен, что ты не доволен;Сила асинхронности F # заключается скорее в асинхронных вычислениях, чем в параллельных задачам процессоре, связанном с процессором (который более приспособлен к Task
s и .NET 4.0 TPL).Вот полный пример:
open System.Diagnostics
open System.IO
open System.Net
open Microsoft.FSharp.Control.WebExtensions
let sites = [|
"http://bing.com"
"http://google.com"
"http://cnn.com"
"http://stackoverflow.com"
"http://yahoo.com"
"http://msdn.com"
"http://microsoft.com"
"http://apple.com"
"http://nfl.com"
"http://amazon.com"
"http://ebay.com"
"http://expedia.com"
"http://twitter.com"
"http://reddit.com"
"http://hulu.com"
"http://youtube.com"
"http://wikipedia.org"
"http://live.com"
"http://msn.com"
"http://wordpress.com"
|]
let print s =
// careful, don't create a synchronization bottleneck by printing
//printf "%s" s
()
let printSummary info fullTimeMs =
Array.sortInPlaceBy (fun (i,_,_) -> i) info
// for i, size, time in info do
// printfn "%2d %7d %5d" i size time
let longest = info |> Array.map (fun (_,_,time) -> time) |> Array.max
printfn "longest request took %dms" longest
let bytes = info |> Array.sumBy (fun (_,size,_) -> float size)
let seconds = float fullTimeMs / 1000.
printfn "sucked down %7.2f KB/s" (bytes / 1024.0 / seconds)
let FetchAllSync() =
let allsw = Stopwatch.StartNew()
let info = sites |> Array.mapi (fun i url ->
let sw = Stopwatch.StartNew()
print "S"
let req = WebRequest.Create(url)
use resp = req.GetResponse()
use stream = resp.GetResponseStream()
use reader = new StreamReader(stream,
System.Text.Encoding.UTF8, true, 4096)
print "-"
let contents = reader.ReadToEnd()
print "r"
i, contents.Length, sw.ElapsedMilliseconds)
let time = allsw.ElapsedMilliseconds
printSummary info time
time, info |> Array.sumBy (fun (_,size,_) -> size)
let FetchAllAsync() =
let allsw = Stopwatch.StartNew()
let info = sites |> Array.mapi (fun i url -> async {
let sw = Stopwatch.StartNew()
print "S"
let req = WebRequest.Create(url)
use! resp = req.AsyncGetResponse()
use stream = resp.GetResponseStream()
use reader = new AsyncStreamReader(stream, // F# PowerPack
System.Text.Encoding.UTF8, true, 4096)
print "-"
let! contents = reader.ReadToEnd() // in F# PowerPack
print "r"
return i, contents.Length, sw.ElapsedMilliseconds })
|> Async.Parallel
|> Async.RunSynchronously
let time = allsw.ElapsedMilliseconds
printSummary info time
time, info |> Array.sumBy (fun (_,size,_) -> size)
// By default, I think .NET limits you to 2 open connections at once
ServicePointManager.DefaultConnectionLimit <- sites.Length
for i in 1..3 do // to warmup and show variance
let time1,r1 = FetchAllSync()
printfn "Sync took %dms, result was %d" time1 r1
let time2,r2 = FetchAllAsync()
printfn "Async took %dms, result was %d (speedup=%2.2f)"
time2 r2 (float time1/ float time2)
printfn ""
На моем 4-ядерном корпусе это дает почти четырехкратное ускорение.
РЕДАКТИРОВАТЬ
В ответ на ваш комментарий ямы обновили код.Вы правы в том, что я добавил больше сайтов и не вижу ожидаемого ускорения (по-прежнему стабильно около 4х).Я начал добавлять небольшой отладочный вывод выше, продолжу исследовать, чтобы проверить, не мешает ли что-то еще соединениям ...
EDIT
Снова отредактировал код.Ну, я нашел то, что может быть узким местом.Вот реализация AsyncReadToEnd в PowerPack:
type System.IO.StreamReader with
member s.AsyncReadToEnd () =
FileExtensions.UnblockViaNewThread (fun () -> s.ReadToEnd())
Другими словами, он просто блокирует поток пула потоков и читает синхронно.Argh !!!Дайте мне посмотреть, смогу ли я обойти это.
EDIT
Хорошо, AsyncStreamReader в PowerPack делает правильные вещи, и я использую это сейчас.
Однакоключевая проблема, кажется, дисперсия .
Когда вы нажимаете, скажем, на cnn.com, большая часть времени возвращается как 500 мс.Но время от времени вы получаете один запрос, который занимает 4 секунды, и это, конечно, потенциально убивает кажущееся асинхронное перфорирование, поскольку общее время - это время самого неудачного запроса.
Запустив программу выше, яувидеть ускорения от 2,5 до 9 раз на моем 2-ядерном корпусе дома.Хотя это очень сильно варьируется.Все еще возможно, что в программе есть какие-то узкие места, которые я пропустил, но я думаю, что разница в сети может объяснить все, что я вижу на данный момент.