Async.Parallel
es casi definitivamente aquí. No estoy seguro de lo que no te gusta; la fuerza de F # asyncs radica más en la informática asincrónica que en las tareas vinculadas a la CPU paralelas a tareas (que está más adaptada a Task
s y .NET 4.0 TPL). Aquí hay un ejemplo completo:
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 ""
En mi caja de 4 núcleos, esto siempre da una aceleración de casi 4x.
EDITAR
En respuesta a su comentario, He actualizado el código. Tienes razón en que he agregado más sitios y no estoy viendo la aceleración esperada (aún manteniéndome estable alrededor de 4x). He empezado a añadir un poco por encima de la salida de depuración, continuará investigando para ver si algo más está estrangulando las conexiones ...
EDITAR
Editted el código de nuevo. Bueno, encontré lo que podría ser el cuello de botella. Aquí está la implementación de AsyncReadToEnd en el PowerPack:
type System.IO.StreamReader with
member s.AsyncReadToEnd() =
FileExtensions.UnblockViaNewThread (fun() -> s.ReadToEnd())
En otras palabras, sólo se bloquea un hilo de subprocesos y lee de forma sincrónica. Argh !!! Déjame ver si puedo evitar eso.
EDITAR
Ok, la AsyncStreamReader en el PowerPack hace lo correcto, y estoy usando ahora.
Sin embargo, la cuestión clave parece ser varianza.
Cuando pulse, digamos, cnn.com, la mayoría de las veces el resultado volverá en 500ms. Pero de vez en cuando obtienes esa solicitud que requiere 4s, y esto, por supuesto, puede acabar con el aparente rendimiento asincrónico, ya que el tiempo total es el momento de la solicitud desafortunada.
Ejecutando el programa anterior, veo aceleraciones de aproximadamente 2,5x a 9x en mi caja de 2 núcleos en casa. Sin embargo, es muy variable. Todavía es posible que haya algún cuello de botella en el programa que me he perdido, pero creo que la varianza de la web puede dar cuenta de todo lo que estoy viendo en este momento.
Uso un código bastante similar. Como una verificación de cordura, agregué un "do! Async, Sleep 1000" en un lugar estratégico. Tengo dos de las cuatro HT desactivadas, y obtengo una aceleración de 4 veces con 4 solicitudes de página Y con 20 solicitudes. Que te dice eso? Se está haciendo tarde aquí. Probaré tu truco DefaultConnectionLimit mañana. – GregC
Agregué un código de muestra que evita las API de acceso web como una prueba de concepto. Como dijiste, Async.Paralelo parece ser el adecuado aquí. – GregC
Para mañana, echaremos un vistazo a las API de acceso web que llegan a localhost. – GregC