朱莉娅中的还原平行循环



我们可以使用

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

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

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

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

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

详细阐述一下 Dan 的观点;要查看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)"

从顶部应该清楚发生了什么。每次迭代都会生成一个输出。当涉及到在这些输出上使用指定的函数时,这是在输出对中完成的。前两对输出被送入打印,然后打印操作的结果成为下一对要处理的第一项。由于输出是nothing的,print不打印任何内容(3,4(。这个 print 语句的结果是nothing,因此下一个要打印的对是nothing(4,5),依此类推,直到所有元素都被消耗掉。即就伪代码而言,这就是正在发生的事情:

第 1 步:状态 = 打印((1,2(, (2,3((; # 状态变为nothing
步骤 2: 状态 = 打印(状态, (3,4((; # 状态再次变为nothing
第 3 步:状态 = 打印(状态,(4,5((; # 等等

字符串按预期工作的原因是,正在执行以下步骤:

第 1 步:状态 = 字符串((1,2(,(2,3((;
第 2 步:状态 = 字符串(状态,(3,4((;
第 3 步:状态 = 字符串(状态,(4,5(;

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

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

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

编辑 - vcat/追加!/索引分配之间的寻址基准

我认为最有效的方法实际上是通过对预分配数组进行正常索引。但是在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等。

最新更新