pythonjoblib&;随机游走-一种[并发]进程调度的性能

pythonjoblib&;随机游走-一种[并发]进程调度的性能,python,performance,parallel-processing,joblib,parallelism-amdahl,python2.x,Python,Performance,Parallel Processing,Joblib,Parallelism Amdahl,Python2.x,下面是我的python-3.6代码,用于模拟一维反射随机游动,使用joblib模块在Linux集群机器上跨K工作者并发生成400个实现 但是,我注意到,K=3的运行时比K=1的运行时差,而K=5的运行时更差 有谁能想办法改进我对joblib的使用吗 from math import sqrt import numpy as np import joblib as jl import os K = int(os.environ['SLURM_CPUS_PER_TASK']) def f(j):

下面是我的python-3.6代码,用于模拟一维反射随机游动,使用
joblib
模块在Linux集群机器上跨
K
工作者并发生成400个实现

但是,我注意到,
K=3
的运行时比
K=1
的运行时差,而
K=5
的运行时更差

有谁能想办法改进我对
joblib
的使用吗

from math import sqrt
import numpy as np
import joblib as jl
import os

K = int(os.environ['SLURM_CPUS_PER_TASK'])

def f(j):
    N = 10**6
    p = 1/3
    np.random.seed(None)
    X = 2*np.random.binomial(1,p,N)-1   # X = 1 with probability p
    s = 0                               # X =-1 with probability 1-p
    m = 0 
    for t in range(0,N):
        s = max(0,s+X[t])
        m = max(m,s)
    return m

pool = jl.Parallel(n_jobs=K)
W = np.asarray(pool(jl.delayed(f)(j) for j in range(0,400)))
W      
改进我使用joblib的方法

joblib
可以帮助
,也会有帮助,但如果这样做的成本低于有效的加速,则只有一个可以从分布式执行中获益的代码可以在一些资源池中分割

调整
joblib
的预加载和批处理大小参数只有在待分发代码的性能得到改善之后才有意义

如下所示,在这方面的一些努力表明,在实现每一次随机行走的
~217000[us]
<217000[us]运行时间方面,
~8x
的核心加速(而不是如上所述的每项
~1640000[us]

只有在这之后,才能在集群资源相关优化(性能破坏避免工作)方面开展一些更艰巨的工作,以实现ab定义的假设意图,即组织上述400次重复的分布式工作流程

只有在以下情况下,这才有意义:

  • 如果可能避免CPU不足,以及
  • 如果不必支付任何额外的分布式作业调度成本

也许是一个很长但很重要的故事,讲述了表演在哪里得救或失去执行摘要对于Gene AMDAHL博士的论点,几乎没有比这更好的奖励了: 上面定义的任务的内部结构非常复杂
[SERIAL]

  • 由于PRNG设计,随机数生成主要是一个纯的过程
  • 1E6
    迭代器
    在一个1D向量上,预先计算醉酒水手的步伐是纯粹的-
    [SERIAL]
是的,“外部”工作范围(相同流程的400次重复)可以很容易地转换为“仅仅”
[并发]
(不是真正的-
[并行]
,即使教授和想要的专家试图告诉你)流程调度,但这样做的附加成本比线性添加到运行时的成本更糟糕,考虑到
[SERIAL]
部分没有重新设计性能,这些努力的净效果很容易破坏最初的良好意图(上面的Q.E.D.随着发布的运行时间的增长,对于
K==1
即使是少量的
K
-s,运行时间也从
10:52
增加到约13分钟)


一个简短的测试已经证明,在使用标准python工具之后,整个任务可以在
<1.45[s]
<1.45[s]
下以纯粹的
方式运行(而不是报道的~12-13分钟),即使在石器时代的老式桌面设备上也是如此(一些缓存内计算效应是可能的,但更多的是意外的副作用,而不是为了特定于HPC集群的性能而有意进行HPC驱动的代码重构):

<1.45[s]

为什么?如何?这就是关于……
的全部故事(HPC的努力可能会使其在1[s]以下更进一步)


Gene AMDAHL博士的论点,即使是在他被广泛引用的报告中,以其原始的、附加的间接费用不可知的形式,也表明,
[SERIAL]
[PARALLEL]
工作块的任何组合都将从为
[PARALLEL]
部分使用的处理单元数量的增加中获得主要有限的好处(也称为收益递减定律,即使是无限多个处理器的加速比也是渐近有限的),
[SERIAL]
部分的任何改进都将继续增加加速比(以纯线性方式)。让我跳过这里的不利影响(也会影响加速,有些是以类似的纯线性方式,但在不利的意义上-附加开销-如下所述)

第一步:
修复代码,使其变得有用。 根据上面的代码,根本不存在随机游动

为什么?

>>> [ op( np.random.binomial( 1, 1 /3,  1E9 ) ) for op in ( sum, min, max, len ) ]
[0, 0, 0, 1000000000]
所以,
代码按原样生成一个相当昂贵的先验已知常量列表。 完全没有随机性。该死的python整数除法取整。
:o)

>>> [ op( np.random.binomial( 1, 1./3., 1E9 ) ) for op in ( sum, min, max, len ) ]
[333338430, 0, 1, 1000000000]

所以这是固定的


第二步:
了解开销(最好也了解隐藏的性能拦截器) 任何试图实例化分布式进程(对于所指示的
K
-amount
joblib
-派生进程中的每一个
都会让您付出代价。总是…

鉴于此,
您的代码执行将获得额外的
[SERIAL]
-附加代码,在任何…仍然只是理论上的…
(1/n_作业)
拆分-效果可能开始发生之前,该附加代码必须运行

仔细观察“有用”的工作:

对于这类工作包,最佳演示用途的加速将从非解释、无GIL、线程化后端、
多处理.Pool
-衍生的Cython-ised代码包、
cdef
-ed和
nogil
指令.Ma进行演示
>>> [ op( np.random.binomial( 1, 1./3., 1E9 ) ) for op in ( sum, min, max, len ) ]
[333338430, 0, 1, 1000000000]
def f( j ):                                         # T0
    #pass;   np.random.seed( None )                 #  +      ~ 250 [us]
    prnGEN = np.random.RandomState()                #  +      ~ 230 [us]
    # = 2 * np.random.binomial( 1, 1./3., 1E6 ) - 1 #  +  ~ 465,000 [us]
    X =        prnGEN.binomial( 1, 1./3., 1E6 )     #  +  ~ 393,000
    X*= 2                                           #  +    ~ 2.940
    X-= 1                                           #  +    ~ 2.940                                  
    s = 0; m = 0                                    #  +        ~ 3 [us]
    for t in range( 0, int( 1E6 ) ):                #     ( py3+ does not allocate range() but works as an xrange()-generator
        s = max( 0, s + X[t] )                      #  +       ~ 15 [us] cache-line friendly consecutive { hit | miss }-rulez here, heavily ...
        m = max( m, s )                             #  +       ~  5 [us]
    return  m                                       # = ~ 2,150,000 [us] @  i5/2.67 GHz
#                                                   # = ~ 1,002,250 [us] @ amd/3.6  GHz
def f( j ):
    ts = time.time()
    #------------------------------------------------------<clock>-ed SECTION
    N = 10**6
    p = 1./3.
    np.random.seed( None )                    # RandomState coordination ...
    X = 2 * np.random.binomial( 1, p, N ) - 1 # X = 1 with probability p
    s = 0                                     # X =-1 with probability 1-p
    m = 0 
    for t in range( 0, N ):
        s = max( 0, s + X[t] )
        m = max( m, s )
    #------------------------------------------------------<clock>-ed SECTION
    return ( m, time.time() - ts )            # tuple
from math import sqrt
import numpy as np

from multiprocessing import Pool
import os

K = int(os.environ['NTASK'])


def f(j):
    N = 10**6
    p = 1./3.
    np.random.seed(None)
    X = 2*np.random.binomial(1,p,N)-1   # X = 1 with probability p
    s = 0                               # X =-1 with probability 1-p
    m = 0 
    for t in range(0,N):
        s = max(0,s+X[t])
        m = max(m,s)
    return m

pool = Pool(processes=K) 
print pool.map(f, xrange(40))
$ time NTASK=1 python stof.py                                                                                                
[21, 19, 17, 17, 18, 16, 17, 17, 19, 19, 17, 16, 18, 16, 19, 22, 20, 18, 16, 17, 17, 16, 18, 18, 17, 17, 19, 17, 19, 19, 16, 16, 18, 17, 18, 18, 19, 20, 16, 19]

real    0m30.367s
user    0m30.064s
sys     0m 0.420s
$ time NTASK=2 python stof.py                                                                                                
[18, 16, 16, 17, 19, 17, 21, 18, 19, 21, 17, 16, 15, 25, 19, 16, 20, 17, 15, 19, 17, 16, 20, 17, 16, 16, 16, 16, 17, 23, 17, 16, 17, 17, 19, 16, 17, 16, 19, 18]

real    0m13.428s
user    0m26.184s
sys     0m 0.348s
$ time NTASK=3 python stof.py 
[18, 17, 16, 19, 17, 18, 20, 17, 21, 16, 16, 16, 16, 17, 22, 18, 17, 15, 17, 19, 18, 16, 15, 16, 16, 24, 20, 16, 16, 16, 22, 19, 17, 18, 18, 16, 16, 19, 17, 18]

real    0m11.946s
user    0m29.424s
sys     0m 0.308s
$ time NTASK=4 python stof.py
[16, 19, 17, 16, 19, 17, 17, 16, 18, 22, 16, 21, 16, 18, 15, 16, 20, 17, 22, 17, 16, 17, 17, 20, 22, 21, 17, 17, 16, 17, 19, 16, 19, 16, 16, 18, 25, 21, 19, 18]

real    0m 8.206s
user    0m26.580s
sys     0m 0.360s