Parallel processing Julia:分区上循环迭代器的并行

Parallel processing Julia:分区上循环迭代器的并行,parallel-processing,julia,Parallel Processing,Julia,所以我试着遍历一些东西的分区列表,比如说对于13到21之间的一些n。理想情况下,我希望运行的代码如下所示: valid_num = @parallel (+) for p in partitions(1:n) int(is_valid(p)) end println(valid_num) s = [1,2,3,4] is_valid(p) = length(p)==2 valid_num = @parallel (+) for i = 1:30 foldl((x,y)->(x

所以我试着遍历一些东西的分区列表,比如说对于13到21之间的一些
n
。理想情况下,我希望运行的代码如下所示:

valid_num = @parallel (+) for p in partitions(1:n)
  int(is_valid(p))
end

println(valid_num)
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
这将使用
@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
(编辑:我认为这是因为如果
p=partitions(1:n)
,你不能调用
p[3]

我曾尝试使用
pmap
来解决这个问题,但由于分区的数量可能会变得非常大,非常快(有250多万个
1:13
分区,当我达到
1:21
时,事情会变得非常大),因此构建如此大的数组成为一个问题。我让它跑了一个晚上,它仍然没有完成

有人对我如何在朱莉娅身上有效地做到这一点有什么建议吗?我可以使用30核左右的计算机,而且我的任务似乎很容易并行化,所以如果有人知道在Julia中实现这一点的好方法,我将不胜感激


非常感谢你

一种方法是将问题分成不太大的部分,然后并行处理每个部分中的项目,例如:

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)
我打算使用该方法从迭代器中实现多达
npar
项,但
take()
似乎已被弃用,因此我将自己的实现包括在内,我称之为
my_take()
。因此,
getPart()
函数使用
my_take()
一次最多获取
npar
分区,并对其进行计算。在本例中,计算只是将它们的长度相加,因为我没有OP的
is\u valid()
函数的代码<代码>获取零件()然后返回结果

由于
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()
,而不是对
循环使用并行

关于内存问题,当我使用4个工作进程运行Julia时,从
分区(1:10)
实现30个项目在我的电脑上占用了近1GB的内存,因此我预计即使实现
分区(1:21)
的一小部分也需要大量内存。在尝试这种计算之前,可能需要估计需要多少内存才能确定是否可能

关于计算时间,请注意:

julia> length(partitions(1:10))
115975

julia> length(partitions(1:21))
474869816156751

。。。因此,即使在30个核上进行高效的并行处理,也不足以在合理的时间内解决更大的问题

下面的代码给出了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迭代器,以获得与前面答案下面的take_every迭代器相同的效果。注意,在这个解决方案中,每个进程都必须计算每个分区。但是,由于每个进程使用不同的参数来删除
,因此不会有两个进程在同一分区上调用有效的

除非您想做大量的数学运算来找出如何实际跳过分区,否则无法避免至少在一个进程上按顺序计算分区。我认为Simon的答案是在一个进程上实现这一点,并分发分区。Mine要求每个工作进程自己计算分区,这意味着计算是重复的。然而,它是并行复制的,这(如果您实际上有30个处理器)不会花费您的时间

以下是有关如何实际计算分区上迭代器的资源:

先前的答案(比必要的更复杂) 我在写我的答案时注意到了西蒙的答案。我们的解决方案似乎与我类似,只是我的解决方案使用迭代器来避免在内存中存储分区。我不确定哪一种会更快,但我认为两种选择都有好处。假设计算是有效的要比计算分区本身花费更长的时间,您可以这样做:

valid_num = @parallel (+) for p in partitions(1:n)
  int(is_valid(p))
end

println(valid_num)
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_each函数返回一个迭代器,该迭代器返回从第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:n)中p的
替换为collect(分区(1:n))中p的
。但是,根据问题的大小,您可能会遇到内存问题。。。另外,我很惊讶您不能迭代
分区的输出,因为我认为这就是要处理的。尽管如此,索引到
分区的输出
在我的机器上也不起作用,所以我显然遗漏了一些东西……我希望我可以。如果我在collect(partitions(1:21))中尝试
但是,我知道我必须这样做,我会遇到内存问题:(迭代器包中的takenth函数()似乎做了TakeEvery正在做的事情。但看看你的代码,我想知道为什么你创建了TakeEvery类型。我在Julia中还没有创建任何数据结构,所以我很好奇什么时候创建/不创建新的数据结构。takenth允许偏移量吗?对我来说似乎不允许,这就是为什么我创建了自己的类型。我总是不喜欢创建自己的数据结构n数据结构,如果可能的话,但是Julia是如此的新,以至于有时我需要的功能还没有实现。我也只是不太了解Julia生态系统中存在的东西。哦,但是看起来它们有一个drop函数,你可以与takenth结合以得到你想要的。编辑了我的答案以使用takenth,dro