【问题标题】:Julia - Sending GLPK.Prob to workerJulia - 将 GLPK.Prob 发送给工人
【发布时间】:2016-08-03 09:19:25
【问题描述】:

我在 Julia 中使用 GLPK,并使用 spencerlyon 编写的 methods

sendto(2, lp = lp) #lp is type GLPK.Prob

但是,我似乎无法在工作人员之间发送类型 GLPK.Prob。每当我尝试发送类型 GLPK.Prob 时,它都会被“发送”并调用

remotecall_fetch(2, whos)

确认 GLPK.Prob 已发送

当我尝试通过调用解决它时出现问题

simplex(lp)

错误

GLPK.GLPKError("invalid GLPK.Prob")

出现。我知道 GLPK.Prob 最初不是无效的 GLPK.Prob,如果我决定在另一个工人 fx 工人 2 上显式构造 GLPK.Prob 类型,调用 simplex 运行得很好

这是一个问题,因为 GLPK.Prob 是从我的自定义类型生成的,有点偏重

tl;dr 是否有一些类型无法在工作人员之间正确发送?

更新

我现在看到了呼唤

remotecall_fetch(2, simplex, lp)

将返回上述 GLPK 错误

此外,我刚刚注意到 GLPK 模块有一个名为

的方法
GLPK.copy_prob(GLPK.Prob, GLPK.Prob, Int)

但在复制 GLPK.Prob 时 deepcopy(当然不是复制)不起作用

示例

function create_lp()
    lp = GLPK.Prob()

    GLPK.set_prob_name(lp, "sample")
    GLPK.term_out(GLPK.OFF)

    GLPK.set_obj_dir(lp, GLPK.MAX)

    GLPK.add_rows(lp, 3)
    GLPK.set_row_bnds(lp,1,GLPK.UP,0,100)
    GLPK.set_row_bnds(lp,2,GLPK.UP,0,600)
    GLPK.set_row_bnds(lp,3,GLPK.UP,0,300)

    GLPK.add_cols(lp, 3)

    GLPK.set_col_bnds(lp,1,GLPK.LO,0,0)
    GLPK.set_obj_coef(lp,1,10)
    GLPK.set_col_bnds(lp,2,GLPK.LO,0,0)
    GLPK.set_obj_coef(lp,2,6)
    GLPK.set_col_bnds(lp,3,GLPK.LO,0,0)
    GLPK.set_obj_coef(lp,3,4)

    s = spzeros(3,3)
    s[1,1] =  1
    s[1,2] =  1
    s[1,3] =  1
    s[2,1] =  10
    s[3,1] =  2
    s[2,2] =  4
    s[3,2] =  2
    s[2,3] =  5
    s[3,3] =  6

    GLPK.load_matrix(lp, s)

    return lp
end 

这将返回一个 lp::GLPK.Prob() ,运行时将返回 733.33

simplex(lp)
result = get_obj_val(lp)#returns 733.33

但是,做

addprocs(1)
remotecall_fetch(2, simplex, lp)

会导致上面的错误

【问题讨论】:

  • 你能发布一个可重现的例子吗?

标签: parallel-processing julia glpk


【解决方案1】:

看起来问题在于您的 lp 对象包含一个指针。

julia> lp = create_lp()
GLPK.Prob(Ptr{Void} @0x00007fa73b1eb330)

不幸的是,使用指针和并行处理很困难——如果不同的进程有不同的内存空间,那么就不清楚进程应该查看哪个内存地址才能访问指针指向的内存。这些问题是可以克服的,但显然它们需要对涉及所述指针的每种数据类型进行单独的工作,有关更多信息,请参阅this GitHub 讨论。

因此,我的想法是,如果您想访问 worker 上的指针,您可以在该 worker 上创建它。例如

using GLPK
addprocs(2)

@everywhere begin
    using GLPK
    function create_lp()
        lp = GLPK.Prob()

        GLPK.set_prob_name(lp, "sample")
        GLPK.term_out(GLPK.OFF)

        GLPK.set_obj_dir(lp, GLPK.MAX)

        GLPK.add_rows(lp, 3)
        GLPK.set_row_bnds(lp,1,GLPK.UP,0,100)
        GLPK.set_row_bnds(lp,2,GLPK.UP,0,600)
        GLPK.set_row_bnds(lp,3,GLPK.UP,0,300)

        GLPK.add_cols(lp, 3)

        GLPK.set_col_bnds(lp,1,GLPK.LO,0,0)
        GLPK.set_obj_coef(lp,1,10)
        GLPK.set_col_bnds(lp,2,GLPK.LO,0,0)
        GLPK.set_obj_coef(lp,2,6)
        GLPK.set_col_bnds(lp,3,GLPK.LO,0,0)
        GLPK.set_obj_coef(lp,3,4)

        s = spzeros(3,3)
        s[1,1] =  1
        s[1,2] =  1
        s[1,3] =  1
        s[2,1] =  10
        s[3,1] =  2
        s[2,2] =  4
        s[3,2] =  2
        s[2,3] =  5
        s[3,3] =  6

        GLPK.load_matrix(lp, s)

        return lp
    end 
end

a = @spawnat 2 eval(:(lp = create_lp()))
b = @spawnat 2 eval(:(result = simplex(lp)))
fetch(b)

有关使用它的更多信息,请参阅下面@spawn 上的文档,因为它可能需要一些时间来适应。



@spawn@spawnat 是 Julia 提供的用于将任务分配给工作人员的两个工具。这是一个例子:

julia> @spawnat 2 println("hello world")
RemoteRef{Channel{Any}}(2,1,3)

julia>  From worker 2:  hello world

这两个宏都将评估工作进程上的expression。两者之间的唯一区别是@spawnat 允许您选择哪个worker 将评估表达式(在上面的示例中指定了worker 2),而@spawn 将根据可用性自动选择一个worker。

在上面的例子中,我们只是让 worker 2 执行 println 函数。从这里返回或检索没有任何有趣的东西。然而,我们发送给 worker 的表达式通常会产生我们想要检索的东西。请注意,在上面的示例中,当我们调用 @spawnat 时,在我们从 worker 2 获得打印输出之前,我们看到了以下内容:

RemoteRef{Channel{Any}}(2,1,3)

这表明@spawnat 宏将返回一个RemoteRef 类型的对象。反过来,这个对象将包含我们的表达式的返回值,该返回值发送给工作人员。如果我们想检索这些值,我们可以先将@spawnat返回的RemoteRef赋值给一个对象,然后使用fetch()函数对RemoteRef类型对象进行操作,来检索存储的结果来自对工人进行的评估。

julia> result = @spawnat 2 2 + 5
RemoteRef{Channel{Any}}(2,1,26)

julia> fetch(result)
7

能够有效使用@spawn 的关键是了解它所操作的expressions 背后的本质。使用@spawn 向工作人员发送命令比直接键入在其中一个工作人员上运行“解释器”或在其上本地执行代码时要键入的内容稍微复杂一些。例如,假设我们希望使用@spawnat 为worker 上的变量赋值。我们可以试试:

@spawnat 2 a = 5
RemoteRef{Channel{Any}}(2,1,2)

成功了吗?好吧,让我们让工人 2 尝试打印a

julia> @spawnat 2 println(a)
RemoteRef{Channel{Any}}(2,1,4)

julia> 

什么也没发生。为什么?我们可以使用上面的fetch() 对此进行更多调查。 fetch() 非常方便,因为它不仅会检索成功的结果,还会检索错误消息。没有它,我们甚至可能不知道出了什么问题。

julia> result = @spawnat 2 println(a)
RemoteRef{Channel{Any}}(2,1,5)

julia> fetch(result)
ERROR: On worker 2:
UndefVarError: a not defined

错误消息说a 没有在worker 2 上定义。但是这是为什么呢?原因是我们需要将赋值操作包装到一个表达式中,然后我们使用@spawn 告诉工作人员进行评估。下面是一个例子,解释如下:

julia> @spawnat 2 eval(:(a = 2))
RemoteRef{Channel{Any}}(2,1,7)

julia> @spawnat 2 println(a)
RemoteRef{Channel{Any}}(2,1,8)

julia>  From worker 2:  2

:() 语法是 Julia 用来指定 expressions 的语法。然后,我们在 Julia 中使用 eval() 函数计算表达式,并使用 @spawnat 宏指示在 worker 2 上计算表达式。

我们也可以得到同样的结果:

julia> @spawnat(2, eval(parse("c = 5")))
RemoteRef{Channel{Any}}(2,1,9)

julia> @spawnat 2 println(c)
RemoteRef{Channel{Any}}(2,1,10)

julia>  From worker 2:  5

这个例子展示了两个额外的概念。首先,我们看到我们还可以使用在字符串上调用的parse() 函数创建一个表达式。其次,我们看到在调用@spawnat 时可以使用括号,这样可以使我们的语法更加清晰和易于管理。

【讨论】:

  • 谢谢,这与我最终所做的差不多。我从 lp 获取每个相关数组并将其传输过来,并在每个创建 lp 的工作人员上定义一个 create_lp() 方法。我仍然想要一种使用 copy_prob 方法并能够将指针(如果这是你可以做的)“移动”到另一个工作人员的方法,因为总是在寻找优雅
  • @isebarn 当然可以。另外,请注意,当您使用 remotecall_fetch() 然后为函数提供参数时,您指定的对象默认情况下将来自调用 remotecall_fetch() 的进程的范围,然后这些对象将被发送到被激活的进程,而不是 remotecall 在工作进程范围内本机使用参数。这也可能导致这些类型的序列化错误。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-09
  • 2012-02-13
  • 1970-01-01
  • 1970-01-01
  • 2017-11-20
  • 2021-04-20
相关资源
最近更新 更多