Julia:并行代码比顺序代码慢,是否有替代remotecall()?

时间:2017-10-25 20:03:47

标签: parallel-processing julia

当我并行运行一个简单的函数(每个工作者具有不同的输入)时,并行化版本代码所花费的时间一直比顺序函数长。

简单的功能是

addprocs(2)
@everywhere function simple(i,Q,vt_0,ct_0)
  a=sum((rand(1:i),vt_0,ct_0))*2
  return a
end

我的代码的并行化版本是

#In Parallel
tic()
N=10000;
v=zeros(N);
c=zeros(N);
vt_0=0;
ct_0=0;

for i=1:N

  v[i]=fetch(remotecall(simple,2,i,1,vt_0,ct_0))

  c[i]=fetch(remotecall(simple,3,i,2,vt_0,ct_0))

  vt_0=v[i]
  ct_0=c[i]

end
toc()

虽然顺序代码只是在循环的每次迭代中调用函数(不是remote call()也不是fetch(),但我正在思考我在Julia中并行调用worker的方式{ {1}}是问题的根源,但我不确定。

任何人都可以帮我弄清楚为什么这个并行代码与调用函数相比如此之慢?或者这个简单的计算不值得并行化吗?

修改
这是顺序代码:

remotecall()

1 个答案:

答案 0 :(得分:3)

即使对于simple()中非常“浅”的计算,Julia文档也建议使用 remotecall_fetch() 而不是fetch( remotecall( ... ) )

  

为此目的存在函数remotecall_fetch()。它相当于fetch(remotecall(...)),但效率更高。

[PARALLEL] - 流程 @spawn / fetch() 费用

这是一个经常被忽视的话题,非常适合积极地认识并试图推理这个主题。实际上,这是对[PARALLEL] - 流程安排最终绩效产生负面影响的常见根本原因。 If indeed interested in details, getting both the mathematical model + an interactive UI-tool to simulate / evaluate the net-effects of the overhead-strict speedup formula on [PARALLEL] code-executions for as much as 2 .. 8000+ CPU-cores, feel free to read more on this here

这里的主要“可疑”是值 - 进程间依赖性:

  • 可能隐藏了一个 - 在系统函数 rand() 中。如果使用加密强大的实现,则每个rand() 的EACH调用必须更新中心的随机源状态。这意味着,由于这个特殊的原因,所有产生的进程都必须已经建立并且还保持了这个中央共享服务的队列,该队列根本不存在(因为它可以使零源 - 在一个简单的 [SEQ] - 代码执行中,随机性状态更新麻烦)但是在<的情况下需要一些额外的隐藏开销(MUTEX / LOCKS / value-update atomics等) strong> [PAR] - 代码执行单元,实际上“共享”此中央资源(隐藏为严格事务性,并发容量为1,运行1:N阻塞逻辑信令,没有回滚...)并且必须执行并强制执行随机源状态的原子安全更新,然后才能提供对随机源服务的任何下一次调用。

  • 另一个是{previous | next} -loop step依赖,在对simple()的一对调用之间相互交叉的一次 - 进程实例。如下图所示,这种相互依赖实际上使得所有可能产生的调用必须被安排为纯[SEQ] - 进程 - 时间表,并且“剩余的”sum()确实没有太多的关系。咀嚼 [PARALLEL] - 进度表。

实际的相互作用程序依赖性图表说明了对最少注入循环的不可行性因此[PARALLEL] - 处理更高效:

//                           +-------------<---- a way to "inject" a for-loop
//                           |  +----------<---- NOT CONSUMED AT ALL
//                           |  |
//                           |  |             +--******* BUT ********* DEPENDENCY
//                           |  |             |
//                           |  |             v
//                           |  |  +-------<-->- v[]-{prior|next}-step DEPENDENCY
//                           |  |  |     +-<-->- c[]-{prior|next}-step DEPENDENCY
//                           |  |  |     |
//          function simple( i, Q, vt_0, ct_0 )
@everywhere function simple( N, Q, vt_0, ct_0, aVEC )
   for     i  = 1:N
      aVEC[i] = sum( (  rand(1:i), vt_0, ct_0 ) )*2
      //   a  = sum( (  rand(1:i), vt_0, ct_0 ) )*2
      // ret a
end

虽然通过{ put!() | take!() }通过tic() .. toc()通道方法进行显式的进程间通信来添加CSP通道可以解决这种被传递的依赖关系,猜测一下,协同程序调度只会增加额外的开销,所以期望支付更多费用得到更少。

关于原始分析的小注:

在所有情况下,建议将 // ------------------------------------------------------------<SuT>-START tic() for i=1:N v[i]=fetch(remotecall(simple,2,i,1,vt_0,ct_0)) c[i]=fetch(remotecall(simple,3,i,2,vt_0,ct_0)) vt_0=v[i] ct_0=c[i] end toc() // ------------------------------------------------------------<SuT>-FINISH -bracket放在测试的代码部分,并避免和排除任何和所有内存分配和类似的非常长的和实际测量执行中的噪声部分:

environment.jersey().register(new LoggingFeature(...));