8 votes

F# Async.Parallel accélère-t-il les calculs ?

La construction "Async.Parallel" permet-elle vraiment d'accélérer les calculs sur un système multi-core ? Les "Tasks" de la TPL .NET sont-ils impliqués d'une manière ou d'une autre ?

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);

Je suppose que certains d'entre vous l'expliqueront de manière théorique, mais j'apprécierais également des tests en situation réelle.

13voto

Tomas Petricek Points 118959

Utiliser F# async pour des tâches purement liées au CPU ne fonctionne que si les tâches effectuent des opérations plus complexes. Si vous essayez de paralléliser un code qui fait quelque chose de très simple, il est préférable d'utiliser PLINQ (et la Task Parallel Library), qui sont plus optimisés pour ce type de problèmes.

Cependant, même dans ce cas, il est difficile d'obtenir un gain de vitesse dans un cas aussi trivial que celui que vous présentez. Si vous voulez expérimenter un peu plus, vous pouvez essayer ceci :

// 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

Notez que l'utilisation de Array.map est beaucoup plus rapide que d'utiliser des expressions de séquence et de convertir ensuite le résultat en tableau. Si vous souhaitez utiliser des opérations plus complexes que le mappage, alors F# PowerPack contient PSeq avec des fonctions similaires à celles du module Seq o List :

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

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

Si vous voulez en savoir plus à ce sujet, j'ai écrit une série de blog sur programmation parallèle en F# récemment.

10voto

Gabe Points 49718

Quoi pmap crée une liste de 300 000 objets de tâches, s'arrange pour qu'ils soient exécutés en parallèle, puis les exécute réellement en parallèle. En d'autres termes, un seul thread va créer 300 000 objets et les mettre en file d'attente dans le pool de threads. Ce n'est qu'ensuite qu'ils s'exécuteront.

Puisque votre tâche est si triviale (un multiplicateur et un diviseur), le coût de la création de la tâche, de son ordonnancement et du traitement de son résultat est le suivant loin plus que la simple exécution du calcul. Cela signifie que le async La métaphore est mal adaptée à cette opération. Il est de loin préférable d'utiliser PLINQ pour cela.

4voto

dave jones Points 316

Avec des calculs aussi simples, il est préférable de ne créer que quelques threads asynchrones (probablement un pour chaque processeur), puis de faire calculer à chacun une partie de votre réponse. Comme l'a répondu Gabe, vous passez tout votre temps à créer des objets de tâche.

En utilisant ce type de plan, j'obtiens des gains de vitesse qui s'adaptent assez bien au nombre de CPU (le maximum que j'ai essayé est de 8... je réalise que cela ne s'adaptera pas toujours).

Écrire un utilitaire pour faire cela demande plus de travail que de simplement appeler PLINQ, je suppose, mais une fois que vous avez une pmap de type utilitaire, vous pouvez le réutiliser facilement.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X