2017-08-15 3 views
2

Lors de l'exécution de code exécutant des calculs parallèles, la sortie devient tronquée: différents messages sont mélangés. Voici un exemple:Pourquoi les impressions sur la console sont-elles mélangées lors des calculs parallèles?

Iteration 1 
Iteration 
Iteration 23 of 19 - Calculating P&L for test window ending at 10/28/1968 12:00:00 AM 

of 
Iteration 4 
Iteration of 
Iteration 5 
Iteration 
Iteration 19 - Calculating P&L for test window ending at of 19 - Calculating P&L for test window ending at 5/29/1974 12:00:00 AM 
6 of 878/18/1971 12:00:00 AM19 - Calculating P&L for test window ending at 3/4/1977 12:00:00 AM 


of 19 of 
of 19 - Calculating P&L for test window ending at 6/25/1985 12:00:00 AM 

Lors de l'exécution du même programme séquentiellement la sortie de la console sort agréable, sans garbling.

impression à la console se fait par cette fonction:

let windowTrainTest (comm: Communication) critFoo count (model: IModel) (assets: Assets) (paramList: Parameters list) = 
    // Deleted some code here 
    if comm = Verbose then 
     let msg1 = sprintf "\nwindowTrainTestPandL: First date: %A, Last date: %A\nBest Criterion: %.2f\n" fDate lDate bestCriterion 
     let msg2 = sprintf "Best Parameters: %A\n" bestParameters 
     printfn "%s" <| msg1 + msg2 

    (pandl, wgts), bestParameters, (["Criterion", bestCriterion]   |> Map.ofList, 
            ["FirstDate", fDate; "LastDate", lDate] |> Map.ofList) 

parallélisation est fait par cette partie du programme:

let pSeqMapi f (xs: seq<'T>) = xs |> PSeq.mapi f 

let trainTest n i (trainSize, fullSize) = 
     let takenAssets = assets |> Assets.take (min fullSize len) 
     lastDate takenAssets 
     |> printfn "\nIteration %d of %d - Calculating P&L for test window ending at %A\n" (i + 1) n 
     paramList 
     |> windowTrainTest comm' critFoo trainSize model takenAssets 

    let mapTrainTest (initSizes: (int * int) list) = 
     let f = trainTest initSizes.Length 
     match calcType with 
     | PSeq -> initSizes |> pSeqMapi f |> List.ofSeq 
     | _ -> initSizes |> Seq.mapi f |> List.ofSeq 

Y at-il un moyen d'éviter ce genre de comportement, pour exemple en rinçant le message à la console?

Répondre

1

Je pense que j'ai trouvé la solution, et elle ne nécessite pas de verrous. Je l'ai remplacé les lignes

lastDate takenAssets 
|> printfn "\nIteration %d of %d - Calculating P&L for test window ending at %A\n" (i + 1) n 

avec

let msg = sprintf "\nIteration %d of %d - Calculating P&L for test window ending at %A\n" (i + 1) n (lastDate takenAssets) 
printfn "%s" msg 

Je laisse à ceux qui sont plus compétents pour offrir une explication.

+2

Je pense que c'est parce que 'printfn"% s "msg' serait équivalent avec' Console.WriteLine (msg) 'en C#. Alors que 'printfn" \ nItation% d de% d - Calcul de P & L pour la fenêtre de test se terminant à% A \ n "(i + 1) n' serait comme appel multiple à' Console.Write' Ecriture sur console est thread safe , d'où le résultat je pense. – xuanduc987

6

Les calculs parallèles s'exécutent sur différents threads, et si un thread est interrompu au milieu d'un printfn et qu'un second thread exécute un printfn avant que le premier thread ne soit à nouveau exécuté, alors leurs sorties seront entrelacées.

La façon la plus simple de résoudre ce problème est de créer une nouvelle fonction qui utilisera le mot-clé lock autour printfn invocations:

let lockObj = new obj() 
let lockedPrintfn msg = lock lockObj (fun _ -> printfn msg) 

Remplacez ensuite tous vos printfn appels avec lockedPrintfn et vous devriez obtenir la sortie sérialisé vous J'attends. Votre performance souffrira un peu car vos threads passeront parfois du temps à attendre le verrouillage printfn, mais tant que vos calculs durent beaucoup plus longtemps que le temps passé à imprimer, vous ne devriez pas remarquer les performances légèrement plus lentes.

+3

Notez que l'objet de verrouillage * doit être créé en dehors de la fonction 'lockedPrintfn'. Si vous créez l'objet de verrouillage à l'intérieur de la fonction, vous finirez par vous verrouiller sur un objet * différent * à chaque fois, ce qui ne sert à rien et n'est en aucun cas un verrou. – rmunn

+1

Notez, que ** if ** * (cit.) ".. si un thread est interrompu au milieu de .. et un second thread s'exécute un .. avant que le premier thread ne soit à nouveau exécuté, alors .." * tel le code ** ne fonctionne pas ** un mode de calcul trully [Parallel], mais est soumis à un ordonnancement ** juste- [Concurrent] **, en fonction de nombreux facteurs concurrents externes (en évitant l'effet net de [Parallel] théorie de l'ordonnancement). (Vendor-optimizing -) - les architectures de processeurs superscalaires pipelinés visent des cibles différentes, que le [Parallel] -computing & il est en effet difficile de contourner les astuces câblées dans les processeurs juste-[Concurrent] – user3666197

+2

... quoi? Oui, le modèle de thread dans .Net est concurrent plutôt que d'utiliser le vrai parallélisme. Mais à part ça, je n'ai que l'idée la plus "vague" de ce que vous venez de dire, @ user3666197. Je pense que tu as trop compressé et je me suis perdu en cours de route. Pourriez-vous réécrire votre commentaire dans un Gist ou quelque chose, où vous n'êtes pas limité à 600 caractères, puis en affichant un lien vers ce Gist? – rmunn