【问题标题】:Why do prints to console get mixed up when doing parallel computations?为什么在进行并行计算时打印到控制台会混淆?
【发布时间】:2017-08-15 05:16:48
【问题描述】:

在运行一些执行并行计算的代码时,输​​出会出现乱码:不同的消息会混淆。这是一个示例:

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

当顺序运行相同的程序时,控制台输出很好,没有乱码。

打印到控制台是通过这个函数完成的:

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 )

并行化由程序的这一部分完成:

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

有没有办法避免这种行为,例如将消息刷新到控制台?

【问题讨论】:

    标签: parallel-processing f# console


    【解决方案1】:

    并行计算在不同的线程上运行,如果一个线程在printfn 的中间被中断,而第二个线程在第一个线程再次运行之前运行printfn,那么它们的输出将被交错。

    处理此问题的最简单方法是创建一个新函数,该函数将在 printfn 调用周围使用 lock 关键字:

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

    然后将所有printfn 调用替换为lockedPrintfn,您应该会得到您期望的序列化输出。由于您的线程偶尔会花费一些时间等待 printfn 锁定,因此您的性能会受到一点影响,但只要您的计算花费的时间比打印输出所花费的时间长得多,您实际上就不应该注意到性能稍慢。

    【讨论】:

    • 请注意,锁对象需要lockedPrintfn函数之外创建。如果在函数内部创建锁对象,则每次都会锁定一个不同的对象,这是没有意义的,实际上根本不是锁。
    • 请注意,if (cit.) ".. 如果一个线程在 .. 中间中断,而第二个线程在 .. 之前运行第一个线程再次运行,然后 .." 这样的代码运行真正的 [Parallel] 计算模式,但受制于 just-[Concurrent] 调度,取决于许多外部并发因素(避免[并行]调度理论的净效应)。 (供应商优化)- 超标量流水线处理器架构针对不同的目标,而不是 [并行] 计算 & 确实很难绕过 [并发] CPU 中的硬连线技巧
    • ...什么?是的,.Net 中的线程模型是并发的,而不是使用真正的并行性。但除此之外,我对你刚才所说的只有模糊的想法,@user3666197。我认为你压缩得太多了,我一路迷路了。您是否介意在不限于 600 个字符的 Gist 或其他内容中重写您的评论,然后发布指向该 Gist 的链接?
    • 看看stackoverflow.com/tags/parallel-processing/info+有引用资源吗? + 已发布的关于 ILPx 的 CPU 硅架构数据表 / 超标量管道分支-(错误)-预测成本(停顿)等,如果要制作专业的 HPC 调整代码部分,所有这些都需要考虑在内尽可能顺利(不被基于硅的多级管道硬连线策略的副作用破坏)[并行]-执行运行?到目前为止,并行计算根本不是任何一堆 [Concurrent] 线程。这是全行业公认的。
    • 还是很难理解你;也许只是你独特的[写作]风格让我失望。 :-) 无论如何,这个问题来自一个(基于他问的其他问题)似乎是一个相对初学者的人,所以我认为可能应该在某个地方对并发和真正并行编程模型之间的区别进行深入讨论否则,因为它可能对 OP 没有任何好处。
    【解决方案2】:

    我想我找到了解决方案,它不需要锁。我换了行

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

    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
    

    我让那些更有知识的人提供解释。

    【讨论】:

    • 我认为这是因为printfn "%s" msg 将等同于 C# 中的Console.WriteLine(msg)。虽然printfn "\nIteration %d of %d - Calculating P&amp;L for test window ending at %A\n" (i + 1) n 就像对Console.Write 的多次调用一样,但写入控制台是线程安全的,因此我认为是结果。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-04-15
    • 2020-07-12
    • 1970-01-01
    • 1970-01-01
    • 2020-06-05
    • 1970-01-01
    • 2019-07-01
    相关资源
    最近更新 更多