并行计算和朱莉娅

时间:2017-04-10 13:52:20

标签: parallel-processing julia

我在Julia中遇到了并行计算的一些性能问题。我是朱莉娅和平行计算的新手。

为了学习,我并行化了一个应该从并行化中获益的代码,但事实并非如此。

该程序估计阵列组件的平均值的平均值,其元素是随机选择的,具有均匀分布。

串行版

tic()
function mean_estimate(N::Int)
   iter = 100000*2
   p = 5000
   vec_mean   = zeros(iter)
   for i = 1:iter
      vec_mean[i]   = mean( rand(p) )
   end
   return mean(vec_mean)
end

a = mean_estimate(0)
toc()

println("The mean is: ", a)

并行版本

addprocs(CPU_CORES - 1)
println("CPU cores ", CPU_CORES)

tic()
@everywhere function mean_estimate(N::Int)
   iter = 100000
   p = 5000
   vec_mean   = zeros(iter)
   for i = 1:iter
      vec_mean[i]   = mean( rand(p) )
   end
   return mean(vec_mean)
end

the_mean = mean(vcat(pmap(mean_estimate,[1,2])...))
toc()

println("The mean is: ", the_mean)

注意:

  • 串行代码第四行中的因子 2 是因为我在具有两个内核的PC中尝试了代码。
  • 我用htop检查了两个核心的使用情况,看起来没问题。

我得到的输出是:

me@pentium-ws:~/average$ time julia serial.jl 
elapsed time: 2.68671022 seconds
The mean is: 0.49999736055814215

real    0m2.961s
user    0m2.928s
sys     0m0.116s

me@pentium-ws:~/average$ time julia -p 2 parallel.jl 
CPU cores 2
elapsed time: 2.890163089 seconds
The mean is: 0.5000104221069994

real    0m7.576s
user    0m11.744s
sys     0m0.308s

我注意到,对于代码的定时部分,串行版本比并行版本略快。此外,总执行时间存在很大差异。

问题

  • 为什么并行版本会变慢? (我做错了什么?)
  • 哪种方法可以并行化这个程序?

注意:我将pmap与vcat一起使用,因为我也希望尝试使用中位数。

感谢您的帮助

修改

我测量了@HighPerformanceMark建议的次数。 tic()/ toc()次数如下。每种情况的迭代次数都是2E6。

Array Size   Single thread   Parallel   Ratio
      5000            2.69       2.89    1.07
   100 000          488.77     346.00    0.71
  1000 000         4776.58    4438.09    0.93

我很困惑为什么阵列大小没有明显的趋势。

1 个答案:

答案 0 :(得分:2)

您应该在评论中支持 prime 注意建议。

正如@ChrisRackauckas指出的那样,类型不稳定是高性能Julia代码的常见障碍。如果您需要高性能代码,请确保您的函数是type-stable。考虑注释函数pmap和/或vcat的返回类型,例如f(pids::Vector{Int}) = mean(vcat(pmap(mean_estimate, pids))) :: Float64或类似内容,因为pmap没有强烈输入其输出。另一种策略是推出自己的并行调度程序。您可以使用pmap源代码作为跳板(请参阅代码here)。

此外,正如@AlexMorley评论的那样,通过包括编译时间,您会混淆性能测量。通常,在Julia中通过运行两次并仅测量第二次运行来测量函数f()的性能。在第一次运行中,JIT编译器在运行之前编译f(),而第二次运行则使用编译的函数。编译会产生(不需要的)性能成本,因此第二次运行的计时会避免测量编译。

如果可能,preallocate all outputs。在您的代码中,您已将每个工作程序设置为分配自己的zeros(iter)及其自己的rand(p)。这可能会产生严重的性能影响。代码草图:

# code mean_estimate as two functions
f(p::Int) = mean(rand(p))
function g(iter::Int, p::Int)
    vec_mean = zeros(iter)
    for i in eachindex(vec_mean)
        vec_mean[i] = f(p)
    end
    return mean(vec_mean)
end

# run twice, time on second run to get compute time
g(200000, 5000)
@time g(200000, 5000)

### output on my machine
#  2.792953 seconds (600.01 k allocations: 7.470 GB, 24.65% gc time)
# 0.4999951853035917

@time宏警告您垃圾收集器在执行期间清理了大量已分配的内存,实际上是几千兆字节。这会导致性能下降。内存分配可能会掩盖串行和并行计算时间之间的任何区别。

最后,请记住,并行计算会导致计划和管理个别工作人员的开销。你的工人正在计算长度为5000的许多随机向量均值的平均值。但是你可以简洁地计算5M条目的平均值(或中位数)

x = rand(5_000_000)
mean(x)
@time mean(x) # 0.002854 seconds (5 allocations: 176 bytes)

所以目前还不清楚你的并行计算方案如何改善串行性能。并行计算通常在阵列真正强大或计算算术强度时提供最佳帮助,而向量意味着可能不属于该域。

最后一点:您可能想要查看SharedArrays,它将数组分布在具有公共内存池的多个工作者或Julia中的实验性multithreading工具中。您可能会发现这些并行框架比pmap更直观。