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
时使用括号,这可能会使我们的语法更加清晰和易于管理。
我在 Julia 中使用 GLPK,并使用由 spencerlyon
编写的 methodssendto(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.33simplex(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
时使用括号,这可能会使我们的语法更加清晰和易于管理。