代码之家  ›  专栏  ›  技术社区  ›  Gunnar

朱莉娅:并行代码比顺序代码慢,有替代remotecall()的方法吗?

  •  2
  • Gunnar  · 技术社区  · 7 年前

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

    简单的功能是

    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() ,我在想,我是如何在朱莉娅和 remotecall() 是问题的根源,但我不确定。

    有谁能帮我找出为什么这个并行代码比只调用函数要慢?或者这个简单的计算不值得并行化吗?

    编辑:
    以下是顺序代码:

    #Sequential
    tic()
    N=10000;
    v=zeros(N,2);
    c=zeros(N,2);
    vt_0=0;
    ct_0=0;
    
    for i=1:N
    
      v[i]=simple(i,1,vt_0,ct_0)
    
      c[i]=simple(i,2,vt_0,ct_0)
    
      vt_0=v[i]
      ct_0=c[i]
    end
    toc()
    
    1 回复  |  直到 7 年前
        1
  •  3
  •   user3666197    7 年前

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

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


    这个 [PARALLEL] -过程 @spawn fetch() 日常开支

    这是一个经常被忽视的话题,很高兴能积极认识到这一主题并尝试对其进行推理。事实上,这是造成不良影响的常见根源 [平行] -进程调度最终性能。 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() . 在使用加密功能强大的实现的情况下,每次调用 兰德() 必须 更新随机性的中心源状态。这意味着,由于这个特殊的原因,所有、实际上所有衍生的进程都必须建立并维护一个到这个中央共享服务的队列,这个队列在简单的 [SEQ] [PAR]

    • 另一个是{prior |next}-循环步骤依赖,在对 简单() -流程实例。如下图所示,这种相互依存实际上使得所有可能产生的调用都必须安排为纯调用 [顺序] -流程计划和“剩余” sum() [平行] -过程时间表。


    实际的相互进程间依赖性

    以便 -处理效率更高:

    //                           +-------------<---- 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
    

    同时通过以下方式添加具有显式进程间通信的CSP通道: { put!() | take!() } 信道方法可能会解决正在通信的这种依赖性,猜猜看,协同路由调度只会增加额外的开销,所以期望付出更多才能得到更少。


    关于原始分析的一个小说明:

    在所有情况下,建议放置一个 tic() .. toc() -括号紧靠测试的代码部分,避免并排除任何和所有内存分配以及实际测量执行中类似的超长和嘈杂部分:

    // ------------------------------------------------------------<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