Parallel processing 如何在Julia中运行简单的并行数组赋值操作?

Parallel processing 如何在Julia中运行简单的并行数组赋值操作?,parallel-processing,julia,differential-equations,differentialequations.jl,Parallel Processing,Julia,Differential Equations,Differentialequations.jl,我必须多次求解微分方程组,迭代一个参数。为此,我在参数列表上运行一个循环,并为每个参数存储解决方案(在时间值数组中计算)。所以我有一个2D数组,在其中存储解决方案(每一行代表一个参数值) 现在,由于任何迭代都与另一个迭代无关,所以我考虑并行进行 这是我的密码: using DifferentialEquations using SharedArrays using DelimitedFiles using Distributed function tf(x,w) return x*si

我必须多次求解微分方程组,迭代一个参数。为此,我在参数列表上运行一个循环,并为每个参数存储解决方案(在时间值数组中计算)。所以我有一个2D数组,在其中存储解决方案(每一行代表一个参数值)

现在,由于任何迭代都与另一个迭代无关,所以我考虑并行进行

这是我的密码:

using DifferentialEquations
using SharedArrays
using DelimitedFiles
using Distributed

function tf(x,w)
    return x*sin(w*x)
end

function sys!(dv,v,w,t)
    dv[1] = w*v[1]
    dv[2] = tf(v[1],w)
end

times = LinRange(0.1,2,25)

params = LinRange(0.1,1.2,100)

sols = SharedArray{Float64,2}((length(times),length(params)))

@distributed for i=1:length(params)
    println(i)
    init_val = [1.0,1.0]
    tspan = (0.0,2.0)
    prob = ODEProblem(sys!,init_val,tspan,params[i])
    sol = solve(prob)
    sols[:,i] .= sol(times)[2,:]
end

writedlm("output.txt",sols)
现在,当我在循环中没有@distributed前缀的情况下运行它时,它运行得非常完美

但是,当我运行这段代码时,println语句不起作用,尽管存储了文件“output.txt”,但它充满了零

我用这种方式从命令行运行代码

julia -p 4 trycode.jl
虽然存储了文件“output.txt”,但它不显示输出,只工作一分钟,什么也不做。这就好像循环从未进入


对于如何设置这个简单的并行循环,我非常感谢您的帮助

你能从threaded for而不是@distributed for中获益吗?这项工作(Julia 1.4):


正如Bill所说,在Julia中考虑并行性有两种主要方式:线程模型,它是在Julia 1.3中引入的,通过
线程@Threads
宏实现共享内存并行,以及使用
分布式@distributed
宏进行分布式处理,它跨不同的Julia进程并行

线程无疑更接近于“automagic”并行加速,只需很少或不需要重新编写代码,这通常是一个很好的选择,尽管我们必须注意确保运行的任何操作都是线程安全的,所以一定要检查结果是否一致

因为您最初的问题是关于分布式并行性的,所以让我也回答一下。如果你做分布式并行,思考发生了什么的最简单的心智模型(我相信)就是想象你在完全独立的Julia REPLs中运行你的代码

以下是适用于
@分布式
模型的代码版本:

using Distributed
addprocs(2)

using SharedArrays
using DelimitedFiles

@everywhere begin 
    using DifferentialEquations

    tf(x,w) = x*sin(w*x)

    function sys!(dv,v,w,t)
        dv[1] = w*v[1]
        dv[2] = tf(v[1],w)
    end

    times = LinRange(0.1,2,25)
    params = LinRange(0.1,1.2,100)
end

sols = SharedArray{Float64,2}((length(times),length(params)))

@sync @distributed for i=1:length(params)
    println(i)
    init_val = [1.0,1.0]
    tspan = (0.0,2.0)
    prob = ODEProblem(sys!,init_val,tspan,params[i])
    sol = solve(prob)
    sols[:,i] .= sol(times)[2,:]
end

sols
发生了什么变化

  • 我在脚本的开头添加了
    addprocs(2)
    。如果您在开始时使用
    p-2
    (或您想要的任何数量的进程),这是不必要的,但是我经常发现,当代码直接在代码中显式地设置并行环境时,对代码进行推理就更容易了。请注意,目前线程无法执行此操作,即在启动JULIA之前需要设置
    JULIA_NUM_threads
    环境变量,并且一旦启动并运行,就无法更改线程数

  • 然后,我将代码的一些部分移动到一个
    @everywhere begin。。。结束
    块。这实际上是同时在所有进程上运行块中包含的操作。回到运行单独Julia实例的心智模型,您必须查看
    @分布式
    循环中的内容,并确保所有函数和变量都在所有进程上定义。例如,为了确保每个流程都知道问题是什么,您需要对所有流程使用微分方程

  • 最后,我将
    @sync
    添加到分布式循环中。这在该项目的文档中被引用。运行带有
    for
    循环的
    @distributed
    宏会为分布式执行生成一个异步绿色线程(
    Task
    )句柄,并向前移动到下一行。由于您希望等待执行实际完成,因此需要同步
    @sync
    。原始代码的问题是,在不等待绿色线程完成(同步)的情况下,它会吞下错误并立即返回,这就是为什么
    sol
    数组为空。如果您运行原始代码,并且只添加
    @sync
    ,则可以看到这一点-然后您将在worker 2上获得一个
    TaskFailedException:-UndevarError:#sys!未定义
    ,它告诉您工作进程不知道您在主进程上定义的功能。实际上,除非您计划并行运行许多这样的分布式循环,否则您几乎总是希望执行
    @sync
    。在分布式循环中使用聚合器函数时,也不需要使用
    @sync
    关键字(循环的
    @distributed(func)形式为1:1000的i)

现在最好的解决方案是什么?答案是我不知道
@threads
是一个很好的选择,可以在不重写代码的情况下快速并行化线程安全操作,目前仍在积极开发和改进中,将来可能会变得更好。分布式标准库中也提供了一些额外的选项,但是这个答案已经足够长了!根据我个人的经验,没有什么可以取代(1)思考问题和(2)基准执行。您需要考虑的是问题的运行时(包括总运行时间和要分发的每个操作的运行时间)以及消息传递/内存访问要求


好处是,虽然您可能需要花费一些精力来思考一些事情,但Julia有很多很好的选择来充分利用每种硬件情况,从一台破旧的、有两个内核的笔记本电脑(就像我在这里输入的一样)到多节点超高性能集群(这使得Julia成为为数不多的编程语言之一——虽然公平地说,这比我或Bill的答案要复杂一些:)

您使用的是Julia的哪个版本?@OscarSmith 1.3.1谢谢!这很有效。我想我还有一个后续问题。有没有办法告诉线程如何分配循环?据我所知,这将循环分成相等的部分,但在我的实际问题中,大参数
using Distributed
addprocs(2)

using SharedArrays
using DelimitedFiles

@everywhere begin 
    using DifferentialEquations

    tf(x,w) = x*sin(w*x)

    function sys!(dv,v,w,t)
        dv[1] = w*v[1]
        dv[2] = tf(v[1],w)
    end

    times = LinRange(0.1,2,25)
    params = LinRange(0.1,1.2,100)
end

sols = SharedArray{Float64,2}((length(times),length(params)))

@sync @distributed for i=1:length(params)
    println(i)
    init_val = [1.0,1.0]
    tspan = (0.0,2.0)
    prob = ODEProblem(sys!,init_val,tspan,params[i])
    sol = solve(prob)
    sols[:,i] .= sol(times)[2,:]
end

sols