我正在尝试在Julia中实现一个简单的并行算法,用于矩阵上的乘加运算。这更像是为了理解如何在Julia中进行并行计算而不是实用性。
最终,这应该实现C = C + A*B
,其中A
,B
和C
是矩阵。我正在使用的代码是:
function MultiplyAdd!(C::SharedArray{Int64,2}, A::SharedArray{Int64,2}, B::SharedArray{Int64,2})
assert(size(A)[2]==size(B)[1])
const p = size(A)[1] #rows(A), rows(C)
const m = size(A)[2] #cols(A), rows(B)
const n = size(B)[2] #cols(C), cols(B)
# thresh is some threshold based on the number of operations required
# to compute C before we should switch to parallel computation.
const thresh = 190
M = 2*p*m*n
if ( M < thresh )
C += A*B # base case for recursion
elseif ( n >= max(p,m) )
# Partition C and B (Vertical Split)
if (iseven(p))
#even partition of C and B
#MultiplyAdd!( C0, A, B0 )
#MultiplyAdd!( C1, A, B1 )
a = @spawn MultiplyAdd!( C[:,1:convert(Int64,p/2)], A, B[:,1:convert(Int64,p/2)] )
b = @spawn MultiplyAdd!( C[:,1:convert(Int64,p-(p/2))], A, B[:,1:convert(Int64,p-(p/2))])
fetch(a), fetch(b)
else
#odd parition of C and B
a = @spawn MultiplyAdd!( C[:,1:convert(Int64,p/2+0.5)], A, B[:,1:convert(Int64,p/2+0.5)] )
b = @spawn MultiplyAdd!( C[:,1:convert(Int64,p-(p/2+0.5))], A, B[:,1:convert(Int64,p-(p/2+0.5))])
fetch(a), fetch(b)
end
elseif ( p >= m )
# Partition C and A (Horizontal Split)
if (iseven(n))
#even partition of C and A
#MultiplyAdd!( C0, A0, B )
#MultiplyAdd!( C1, A1, B )
a = @spawn MultiplyAdd!(C[1:convert(Int64,n/2),:],A[1:convert(Int64,n/2),:],B)
b = @spawn MultiplyAdd!(C1 = C[1:convert(Int64,n-(n/2)),:], A[1:convert(Int64,n-(n/2)),:], B)
fetch(a), fetch(b)
else
#odd parition of C and A
# MultiplAdd!( C0, A0, B )
# MultiplyAdd!( C1, A1, B )
a = @spawn MultiplyAdd!( C[1:convert(Int64,n/2 + 0.5),:], A[1:convert(Int64,n/2),:], B )
b = @spawn MultiplyAdd!( C[1:convert(Int64,n-(n/2 + 0.5)),:], A[1:convert(Int64,n-(n/2 + 0.5)),:], B )
fetch(a), fetch(b)
end
else
#Begin Serial Recursion
# A is Vertical Split, B is Horizontal Split
#MultiplyAdd!( C,A0,B0 )
#MultiplyAdd!( C,A1,B1 )
if (iseven(m))
MultiplyAdd!( C,A[:,1:convert(Int64,m/2)], B[1:convert(Int64,m/2),:] )
MultiplyAdd!( C,A[:,1:convert(Int64,m-(m/2))], B[1:convert(Int64,m-(m/2) ),:] )
else
MultiplyAdd!( C,A[:,1:convert(Int64,m/2 + 0.5)], B[1:convert(Int64,m/2 + 0.5), :])
MultiplyAdd!( C,A[:,1:convert(Int64,m-(m/2 + 0.5))], B[1:convert(Int64,m-(m/2 + 0.5)), :] )
end
end
end
首先,这根本不起作用。我运行时遇到以下错误。
LoadError: On worker 5:
UndefVarError: #MultiplyAdd! not defined
in deserialize_datatype at ./serialize.jl:823
in handle_deserialize at ./serialize.jl:571
in collect at ./array.jl:307
in deserialize at ./serialize.jl:588
in handle_deserialize at ./serialize.jl:581
in deserialize at ./serialize.jl:541
in deserialize_datatype at ./serialize.jl:829
in handle_deserialize at ./serialize.jl:571
in deserialize_msg at ./multi.jl:120
in message_handler_loop at ./multi.jl:1317
in process_tcp_streams at ./multi.jl:1276
in #618 at ./event.jl:68
while loading In[32], in expression starting on line 73
in #remotecall_fetch#606(::Array{Any,1}, ::Function, ::Function, ::Base.Worker, ::Base.RRID, ::Vararg{Any,N}) at ./multi.jl:1070
in remotecall_fetch(::Function, ::Base.Worker, ::Base.RRID, ::Vararg{Any,N}) at ./multi.jl:1062
in #remotecall_fetch#609(::Array{Any,1}, ::Function, ::Function, ::Int64, ::Base.RRID, ::Vararg{Any,N}) at ./multi.jl:1080
in remotecall_fetch(::Function, ::Int64, ::Base.RRID, ::Vararg{Any,N}) at ./multi.jl:1080
in call_on_owner(::Function, ::Future, ::Int64, ::Vararg{Int64,N}) at ./multi.jl:1130
in fetch(::Future) at ./multi.jl:1156
in MultiplyAdd!(::SharedArray{Int64,2}, ::SharedArray{Int64,2}, ::SharedArray{Int64,2}) at ./In[32]:24
其次,在我看来,我不应该执行两个@spawn
任务。在这些情况中,我应该让第二个只是MultiplyAdd!(C,A,B)
来电。换句话说,只需指定a
和fetch(a)
。
第三,Julia通过引用将数组传递给函数,因此所有操作都不会自然地在相同的C
,A
和B
矩阵上运行?就目前而言,采取如下的切片:
C0 = C[:, 1:p/2]
C1 = C[:, 1:p-p/2]
创建一个全新的对象,它解释了在上面的代码中获取函数调用中的切片。从本质上讲,我正在避免分配尝试并始终对同一个对象进行操作。必须有比我实施的更好的方法。最终,我希望在内存中运行相同的数据,只需“在数组上移动放大镜”就可以并行处理它的子集。
答案 0 :(得分:1)
这里很难帮助你,因为你没有真正问过一个问题。冒着听起来居高临下的风险,我建议你偷看suggestions来问一个好的SO问题。
至于您的代码,您的方法存在一些问题。
MultiplyAdd!
仅与最多两名工人并行。MultiplyAdd!
执行多个调用,例如分配新数组的A[:,1:n]
。A[:,1:n]
之类的调用会生成Array
个对象,而不是SharedArray
个对象,所以对MultiplyAdd!
的递归调用,参数严格类型为SharedArray{Int,2}
不会起作用。MultiplyAdd!
不尊重SharedArray
个对象的索引方案。最后一点很重要。要求工作人员1访问分配给工作人员2的A
和B
部分需要数据传输,这会通过并行化代码来消除任何性能提升。你可以通过运行
for i in procs(A)
@show i, localindexes(A)
end
在SharedArray
对象A
上。理想情况下,每个工作者i
应仅在其自己的本地索引上运行,但允许在本地索引边界进行数据共享可以帮助您节省一些簿记费用。
如果您坚持使用SharedArray
s作为原型,那么您仍然可以选择。 SharedArray
documentation有一些很好的建议。我发现了构造
function f(g, S::SharedArray)
@sync begin
for p in procs(S)
@async begin
remotecall_fetch(g, p, S, p)
end
end
end
S
end
使用某些内核函数g
(例如MultiplyAdd!
)通常会在所有参与工作者之间很好地并行化操作。显然你必须决定如何划分执行;文档中的advection_shared!
示例是一个很好的指南。
您也可以考虑使用Julia的native multithreading。该并行框架与共享内存计算略有不同。但是,它允许您使用熟悉的迭代结构直接对Array
个对象进行操作。