所以我试图迭代某些分区列表,对于13到21之间的某些1:n
来说n
。理想情况下我希望运行的代码看起来像这样:
valid_num = @parallel (+) for p in partitions(1:n)
int(is_valid(p))
end
println(valid_num)
这会使用@parallel for
来映射 - 减少我的问题。例如,将其与Julia文档中的示例进行比较:
nheads = @parallel (+) for i=1:200000000
Int(rand(Bool))
end
但是,如果我尝试修改循环,我会收到以下错误:
ERROR: `getindex` has no method matching getindex(::SetPartitions{UnitRange{Int64}}, ::Int64)
in anonymous at no file:1433
in anonymous at multi.jl:1279
in run_work_thunk at multi.jl:621
in run_work_thunk at multi.jl:630
in anonymous at task.jl:6
我认为这是因为我试图迭代不属于1:n
形式的内容(编辑:我认为它是因为如果{{1}你无法调用p[3]
})。
我尝试使用p=partitions(1:n)
来解决这个问题,但是因为分区的数量可以变得非常大,非常快(pmap
的分区超过250万个,当我得到1:13
事情将是巨大的,构建这样一个大型数组成为一个问题。我让它一直运行过夜,它仍然没有完成。
有没有人对如何在朱莉娅有效地做到这一点有任何建议?我可以访问~30核心计算机,我的任务似乎很容易并行化,所以如果有人知道在Julia中做到这一点的好方法,我将非常感激。
非常感谢你!
答案 0 :(得分:2)
下面的代码给出了511,一组10的大小为2的分区数。
using Iterators
s = [1,2,3,4,5,6,7,8,9,10]
is_valid(p) = length(p)==2
valid_num = @parallel (+) for i = 1:30
sum(map(is_valid, takenth(chain(1:29,drop(partitions(s), i-1)), 30)))
end
此解决方案结合了takenth,drop和chain迭代器,以获得与PREVIOUS ANSWER下面的take_every迭代器相同的效果。请注意,在此解决方案中,每个进程都必须计算每个分区。但是,因为每个进程对drop
使用不同的参数,所以在同一个分区上没有两个进程会调用is_valid。
除非你想要弄清楚如何实际跳过分区,否则无法避免在至少一个进程上按顺序计算分区。我认为Simon的答案在一个进程上执行此操作并分发分区。我要求每个工作进程自己计算分区,这意味着计算正在重复。但是,它是并行复制的(如果你实际上有30个处理器)不会花费你的时间。
以下是有关如何实际计算分区上的迭代器的资源:http://www.informatik.uni-ulm.de/ni/Lehre/WS03/DMM/Software/partitions.pdf。
在写我的时候,我注意到了西蒙的回答。我的解决方案看起来与我类似,除了我使用迭代器来避免在内存中存储分区。我不确定哪种尺寸设置实际上会更快,但我认为这两种选择都很好。假设计算is_valid比计算分区本身需要更长的时间,你可以这样做:
s = [1,2,3,4]
is_valid(p) = length(p)==2
valid_num = @parallel (+) for i = 1:30
foldl((x,y)->(x + int(is_valid(y))), 0, take_every(partitions(s), i-1, 30))
end
它给了我7,一组4的大小为2的分区数.take_every函数返回一个迭代器,它返回以第i个开头的每30个分区。这是代码:
import Base: start, done, next
immutable TakeEvery{Itr}
itr::Itr
start::Any
value::Any
flag::Bool
skip::Int64
end
function take_every(itr, offset, skip)
value, state = Nothing, start(itr)
for i = 1:(offset+1)
if done(itr, state)
return TakeEvery(itr, state, value, false, skip)
end
value, state = next(itr, state)
end
if done(itr, state)
TakeEvery(itr, state, value, true, skip)
else
TakeEvery(itr, state, value, false, skip)
end
end
function start{Itr}(itr::TakeEvery{Itr})
itr.value, itr.start, itr.flag
end
function next{Itr}(itr::TakeEvery{Itr}, state)
value, state_, flag = state
for i=1:itr.skip
if done(itr.itr, state_)
return state[1], (value, state_, false)
end
value, state_ = next(itr.itr, state_)
end
if done(itr.itr, state_)
state[1], (value, state_, !flag)
else
state[1], (value, state_, false)
end
end
function done{Itr}(itr::TakeEvery{Itr}, state)
done(itr.itr, state[2]) && !state[3]
end
答案 1 :(得分:1)
一种方法是将问题分成不太大而无法实现的部分,然后并行处理每个部分中的项目,例如,如下:
function my_take(iter,state,n)
i = n
arr = Array[]
while !done(iter,state) && (i>0)
a,state = next(iter,state)
push!(arr,a)
i = i-1
end
return arr, state
end
function get_part(npart,npar)
valid_num = 0
p = partitions(1:npart)
s = start(p)
while !done(p,s)
arr,s = my_take(p,s,npar)
valid_num += @parallel (+) for a in arr
length(a)
end
end
return valid_num
end
valid_num = @time get_part(10,30)
我打算使用take()方法从迭代器中实现最多npar
项,但take()
似乎已被弃用,所以我已经包含了我自己的实现,我和#39; ve称为my_take()
。因此getPart()
函数使用my_take()
一次获取最多npar
个分区,并对它们进行计算。在这种情况下,计算只会增加它们的长度,因为我没有OP的is_valid()
函数的代码。 get_part()
然后返回结果。
由于length()
计算不是非常耗时,因此在并行处理器上运行时,此代码实际上比在单个处理器上运行时慢:
$ julia -p 1 parpart.jl
elapsed time: 10.708567515 seconds (373025568 bytes allocated, 6.79% gc time)
$ julia -p 2 parpart.jl
elapsed time: 15.70633439 seconds (548394872 bytes allocated, 9.14% gc time)
或者,可以在问题的每个部分使用pmap()
,而不是并行for
循环。
关于内存问题,当我使用4个工作进程运行Julia时,从partitions(1:10)
实现30个项目在我的PC上占用了近1 GB的内存,因此我希望实现partitions(1:21)
的一小部分内容将需要大量的记忆。在尝试这样的计算之前,可能需要估计需要多少内存来查看它是否完全可能。
关于计算时间,请注意:
julia> length(partitions(1:10))
115975
julia> length(partitions(1:21))
474869816156751
...因此,即使在30个核心上进行有效的并行处理也可能不足以使更大的问题在合理的时间内得到解决。