2011-01-31 13 views
8

¿La construcción "Async.Parallel" realmente ayuda a hacer cálculos más rápidos en un sistema multi-core? ¿Las "Tareas" .NET TPL están involucradas aquí de alguna manera?¿F # Async.Parallel acelera los cálculos?

open System; 

let key = Console.ReadKey(true); 
let start = System.DateTime.Now 

let pmap f l = seq { for a in l do yield async {return f a} } |> Async.Parallel |> Async.RunSynchronously 
let map f l = seq {for a in l do yield f a} 

let work f l = 
match key.KeyChar with 
    | '1' -> pmap f l 
    | '2' -> Seq.toArray (map f l) 
    | _ -> [||] 

let result = work (fun x -> (x * x)/75) (seq { 1 .. 100000*3}) 
let endtime = DateTime.Now - start 

printfn "%A"endtime; 
let pause = Console.ReadKey(true); 

Supongo que algunos de ustedes lo explicarán teóricamente, pero también agradeceré algunas pruebas del mundo real.

+0

Con la secuencia reforzada hasta 60000000, se ejecuta 10 segundos con el mapa y se bloquea con la falta de memoria en pmap después de 40 segundos más o menos. Ambas versiones utilizan solo uno de mis dos núcleos. Ahora necesito un F # gurú para explicar esto :-) – TToni

Respuesta

9

Lo que pmap está haciendo es crear una lista de 300,000 objetos de tareas, organizarlos para que se ejecuten en paralelo, y solo entonces ejecutarlos en paralelo. En otras palabras, un único hilo se sentará allí creando 300,000 objetos y poniéndolos en cola en el grupo de subprocesos. Solo entonces ejecutarán.

Dado que su tarea es tan trivial (una multiplicación y una división), la sobrecarga de crear la tarea, programarla y manejar su resultado es ahora más que solo ejecutar el cálculo. Esto significa que la metáfora async no es adecuada para esta operación. Es mucho mejor usar PLINQ para esto.

3

Con cálculos así de simples, una mejor manera es crear solo unos pocos subprocesos asíncronos (probablemente uno para cada CPU), y luego hacer que cada uno calcule parte de su respuesta. Como respondió Gabe, pasas todo tu tiempo creando objetos de tareas.

El uso de este tipo de un plan, me sale aceleraciones que escalan muy de cerca con el número de CPU (lo máximo que he probado es 8 ... Me di cuenta que no se escala para siempre)

Escribir una utilidad para hacer esto es más trabajo que simplemente llamar a PLINQ, supongo, pero una vez que tenga una utilidad de tipo pmap, puede reutilizarlo fácilmente.

12

El uso de F # async para tareas puramente vinculadas a la CPU solo funciona si las tareas realizan una operación más complicada. Si intentas paralelizar código que hace algo muy simple, entonces es mejor usar PLINQ (y la Biblioteca de tareas paralelas), que están más optimizados para este tipo de problemas.

Sin embargo, incluso entonces, obtener aceleración en un caso trivial como el que tiene es difícil. Si desea experimentar con esto un poco más, puede intentar esto:

// Turn on timing in F# interactive 
#time 
let data = [| 1 .. 5000000*3 |] 

// Use standard 'map' function for arrays 
let result = Array.map (fun x -> (x * x)/75) data 
// Use optimized parallel version 
let result = Array.Parallel.map (fun x -> (x * x)/75) data 

Tenga en cuenta que el uso de Array.map en sí es mucho más rápido que el uso de expresiones de secuencia y luego convertir el resultado en una matriz. Si desea utilizar las operaciones más complejas que la cartografía, entonces F # PowerPack contiene PSeq módulo con funciones similares a las de Seq o List:

#r @"FSharp.PowerPack.Parallel.Seq.dll" 

data 
|> PSeq.map (fun a -> ...) 
|> PSeq.filter (fun a -> ...) 
|> PSeq.sort 
|> Array.ofSeq 

Si desea leer más sobre esto, escribí una serie blog sobre parallel programming in F# recientemente.

+5

Tomás se olvidó de mencionar que escribió una serie de publicaciones en el blog sobre F # y el paralelismo aquí: http://tomasp.net/blog/fsharp-parallel-samples.aspx –

+0

enlace proporcionado en respuesta está muerto – Maslow

+0

@Maslow Fixed .. –

Cuestiones relacionadas