当我并行运行一个简单函数(每个工作线程具有不同的输入)时,并行版本代码所需的时间始终比顺序运行的时间长。
简单的功能是
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 中使用 remotecall()
并行调用 worker 是问题的根源,但我不确定。
任何人都可以帮我弄清楚为什么这个并行代码与调用函数相比如此慢?或者这个简单的计算不值得并行化吗?
编辑:
这是顺序代码:
#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()
最佳答案
即使对于 simple()
内的非常“浅”计算的情况,Julia 文档也建议使用 remotecall_fetch()
而不是 fetch(remotecall( ... ) )
:
The function
remotecall_fetch()
exists for this purpose. It is equivalent tofetch(remotecall(...))
but is more efficient.
[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()
的调用都必须更新中央随机源状态。这意味着,由于这个特殊原因,所有、实际上所有生成的进程都必须建立并维护一个到该中央共享服务的队列,该队列根本不会出现(因为它可以使零源)随机状态更新麻烦)在一个简单的[SEQ]
-代码执行中,但需要一些额外的隐藏开销(MUTEX/LOCKS/value-updateatomics等)在[PAR]
代码执行单元的情况下,实际上“共享”此中央资源(被隐藏为严格的事务性,并发容量为 1 ,操作 1:N 阻塞逻辑信令,无回滚...)并且必须在下次调用随机源之前执行并强制执行随机源状态的原子安全更新' 服务可能会得到提供。另一个是 {prior|next} 循环步骤依赖性,在对
simple()
流程实例的调用对之间多次相互交叉。如下图所示,这种相互依赖实际上使得所有潜在的派生调用都必须排列为纯[SEQ]
-process-schedule,而“剩余”sum()
在[PARALLEL]
-流程调度中确实没有多少肉可咀嚼。
实际的进程间相互依赖
插图描述了至少不可能注入(inject)循环
以便使[并行]
-处理更高效:
// +-------------<---- 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!() | 添加具有显式进程间通信的 CSP channel 时take!() }
channel 方法可以解决这种正在通信的依赖关系,猜猜看,协程调度只会增加额外的开销,因此期望付出更多以获得更少。
关于原始分析的小注释:
在所有情况下,建议将 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
关于parallel-processing - Julia:并行代码比顺序代码慢,有remotecall()的替代方法吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46941164/