Julia - 发送 GLPK.Prob 给工作人员

Julia - Sending GLPK.Prob to worker

我在 Julia 中使用 GLPK,并使用由 spencerlyon

编写的 methods
sendto(2, lp = lp) #lp is type GLPK.Prob

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

remotecall_fetch(2, whos)

确认 GLPK.Prob 已发送

当我试图通过调用

来解决问题时出现了问题
simplex(lp)

错误

GLPK.GLPKError("invalid GLPK.Prob")

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

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

tl;dr 是否有某些类型无法在 worker 之间正确发送?

更新

我现在看到那个电话

remotecall_fetch(2, simplex, lp)

会return上面的GLPK错误

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

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

但是在复制 GLPK.Prob

时,深度复制(当然不是复制)将不起作用

例子

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 

这将 return a lp::GLPK.Prob() 当 运行

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

然而,做

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

会导致上面的错误

看起来问题是您的 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 允许您选择哪个工人将评估表达式(在上面的示例中指定了工人 2),而 @spawn 将根据可用性自动选择工人.

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

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

这表明@spawnat宏将return一个RemoteRef类型的对象。该对象又将包含我们发送给 worker 的表达式中的 return 值。如果我们想检索这些值,我们可以先将 RemoteRef@spawnat return 分配给一个对象,然后使用 fetch() 函数操作RemoteRef 类型对象,用于检索从对工人执行的评估中存储的结果。

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

julia> fetch(result)
7

能够有效使用 @spawn 的关键是了解它所操作的 expressions 背后的本质。使用 @spawn 向工作人员发送命令比直接输入您在其中一名工作人员上 运行 和 "interpreter" 或在他们身上执行代码时直接输入的内容稍微复杂一些。例如,假设我们希望使用 @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 时使用括号,这可能会使我们的语法更加清晰和易于管理。