减少朱莉娅的并行循环

时间:2017-08-23 04:29:10

标签: parallel-processing julia

我们可以使用

c = @parallel (vcat) for i=1:10
          (i,i+1)
       end

但是当我尝试使用push!()而不是vcat()时,我遇到了一些错误。如何在此并行循环中使用push!()

c = @parallel (push!) for i=1:10
     (c, (i,i+1))
end

2 个答案:

答案 0 :(得分:3)

@parallel有点类似于foldl(op, itr),因为它使用itr的第一个值作为op的初始第一个参数。 push!缺少操作数之间所需的对称性。也许您正在寻找的是:

julia> c = @parallel (append!) for i=1:10
            [(i,i+1)]
       end

答案 1 :(得分:2)

详细阐述丹的观点;要查看parallel宏的工作原理,请查看以下两个调用之间的区别:

julia> @parallel print for i in 1:10
         (i,i+1)
       end
(1, 2)(2, 3)nothing(3, 4)nothing(4, 5)nothing(5, 6)nothing(6, 7)nothing(7, 8)nothing(8, 9)nothing(9, 10)nothing(10, 11)

julia> @parallel string for i in 1:10
         (i,i+1)
       end
"(1, 2)(2, 3)(3, 4)(4, 5)(5, 6)(6, 7)(7, 8)(8, 9)(9, 10)(10, 11)"

从最顶层开始,应该清楚发生了什么。每次迭代都会产生一个输出。在这些输出上使用指定的功能时,这是在输出对中完成的。输入两个第一对输出进行打印,然后打印操作的结果成为下一对要处理的第一项。由于输出为nothingprint只打印(3,4)。此打印语句的结果为nothing,因此要打印的下一对是nothing(4,5),依此类推,直到消耗掉所有元素。即就伪代码而言,这就是发生的事情:

步骤1:state = print((1,2),(2,3)); #state变为nothing
第2步:state = print(state,(3,4)); #start再次成为nothing 第3步:state = print(state,(4,5)); #等等

字符串按预期工作的原因是因为发生了以下步骤:

步骤1:state = string((1,2),(2,3));
第2步:state = string(state,(3,4));
第3步:state = string(state,(4,5);

通常,传递给并行宏的函数应该是两个相同类型的输入,并输出相同类型的对象

因此您不能使用push!,因为它总是使用两个不同类型的输入(一个数组和一个普通元素),并输出一个数组。因此,您需要使用符合规范的append!

另请注意,不保证输出顺序。 (这里恰好是因为我只使用了1名工人)。如果你想要一些操作顺序很重要的东西,那么你就不应该使用这个结构。例如,显然在添加之类的东西中它并不重要,因为加法是完全相关的操作;但是如果我使用string,如果输出按不同的顺序处理,那么显然你最终会得到一个不同于你所期望的字符串。

编辑 - 解决vcat / append之间的基准! /索引作业

我认为最有效的方法是通过正常索引到预分配的数组。但是在append!vcat之间,追加将肯定会更快,因为vcat总是复制(据我理解)。

基准:

function parallelWithVcat!( A::Array{Tuple{Int64, Int64}, 1} )
  A = @parallel vcat for i = 1:10000
    (i, i+1)
  end
end;

function parallelWithFunction!( A::Array{Tuple{Int64, Int64}, 1} )
  A = @parallel append! for i in 1:10000
    [(i, i+1)];
  end
end;

function parallelWithPreallocation!( A::Array{Tuple{Int64, Int64}, 1} )
  @parallel for i in 1:10000
    A[i] = (i, i+1);
  end
end;

A = Array{Tuple{Int64, Int64}, 1}(10000);

### first runs omitted, all benchmarks here are from 2nd runs ###
# first on a single worker:

@time for n in 1:100; parallelWithVcat!(A); end
#>  8.050429 seconds (24.65 M allocations: 75.341 GiB, 15.42% gc time)

@time for n in 1:100; parallelWithFunction!(A); end
#>  0.072325 seconds (1.01 M allocations: 141.846 MiB, 52.69% gc time)

@time for n in 1:100; parallelWithPreallocation!(A); end
#>  0.000387 seconds (4.21 k allocations: 234.750 KiB)

# now with true parallelism:
addprocs(10);

@time for n in 1:100; parallelWithVcat!(A); end
#>  1.177645 seconds (160.02 k allocations: 109.618 MiB, 0.75% gc time)

@time for n in 1:100; parallelWithFunction!(A); end
#>  0.060813 seconds (111.87 k allocations: 70.585 MiB, 3.91% gc time)

@time for n in 1:100; parallelWithPreallocation!(A); end
#>  0.058134 seconds (116.16 k allocations: 4.174 MiB)

如果有人能提出更有效的方法,请这样做!

特别注意索引的赋值比其余的快得多,因此它出现(至少在这个例子中)并行情况下的大多数计算似乎都在并行化本身丢失了。

免责声明:我没有声称以上是@parallel法术的正确召唤。我没有详细研究宏的内部工作,以便能够另外声明。特别是,我不知道宏导致哪些部分远程处理与本地处理(例如分配部分)。建议谨慎,ymmv等