Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/359.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
使用numpy/scipy最小化Python多处理池中的开销_Python_Numpy_Parallel Processing_Multiprocessing_Pool - Fatal编程技术网

使用numpy/scipy最小化Python多处理池中的开销

使用numpy/scipy最小化Python多处理池中的开销,python,numpy,parallel-processing,multiprocessing,pool,Python,Numpy,Parallel Processing,Multiprocessing,Pool,我花了好几个小时尝试并行化我的数字运算代码,但当我这样做的时候,它只会变得更慢。不幸的是,当我尝试将其简化为下面的示例时,问题就消失了,我真的不想在这里发布整个程序。所以问题是:在这种类型的程序中,我应该避免哪些陷阱 (注:Unutbu回答后的跟进在底部。) 情况如下: 它是关于一个模块,它定义了一个包含大量内部数据的类BigData。在该示例中,有一个插值函数列表ff;在实际的程序中,有更多的,例如,ffA[k],ffB[k],ffC[k] 计算将被归类为“令人尴尬的并行”:每次可以在较小的

我花了好几个小时尝试并行化我的数字运算代码,但当我这样做的时候,它只会变得更慢。不幸的是,当我尝试将其简化为下面的示例时,问题就消失了,我真的不想在这里发布整个程序。所以问题是:在这种类型的程序中,我应该避免哪些陷阱

(注:Unutbu回答后的跟进在底部。)

情况如下:

  • 它是关于一个模块,它定义了一个包含大量内部数据的类
    BigData
    。在该示例中,有一个插值函数列表
    ff
    ;在实际的程序中,有更多的,例如,
    ffA[k]
    ffB[k]
    ffC[k]
  • 计算将被归类为“令人尴尬的并行”:每次可以在较小的数据块上完成工作。在本例中,这是
    do\u chunk()
  • 在我的实际程序中,示例中显示的方法将导致最差的性能:每个块大约1秒(在单个线程中完成时,实际计算时间大约为0.1秒)。因此,对于n=50,
    do_single()
    将在5秒内运行,
    do_multi()
    将在55秒内运行
  • 我还试图通过将
    xi
    yi
    数组分割成连续的块,并迭代每个块中的所有
    k
    值来分割工作。那就好一点了。现在,无论我使用1、2、3或4个线程,总执行时间都没有差别。但当然,我希望看到实际的加速
  • 这可能与:。然而,在程序的其他地方,我使用了一个多处理池来进行更为孤立的计算:一个函数(未绑定到类),看起来像
    def do_chunk(array1,array2,array3)
    ,只对该数组进行numpy计算。在那里,有一个显著的速度提升
  • CPU使用率随着预期的并行进程数而变化(三个线程的CPU使用率为300%)
输出:

计时在运行64位Linux的Intel Core i3-3227 CPU上,该CPU具有2核4线程。对于实际的程序,多进程版本(池机制,即使只使用一个内核)比单进程版本慢10倍

跟进

Unutbu的回答让我走上了正确的道路。在实际的程序中,
self
被pickle到一个37到140 MB的对象中,该对象需要传递给工作进程。更糟糕的是,Python的酸洗速度非常慢;酸洗本身花费了几秒钟的时间,这发生在传递给工作进程的每一块工作上。除了清理和传递大数据对象,Linux中的
apply\u async
开销非常小;对于一个小函数(添加几个整数参数),每个
apply\u async
/
get
对只需要0.2毫秒。因此,将工作分成非常小的部分本身并不是问题。所以,我将所有大数组参数作为索引传输到全局变量。为了优化CPU缓存,我将块大小保持在较小的位置

全局变量存储在全局
dict
中;在设置工作池后,将立即在父进程中删除这些条目。只有
dict
的键被传输到工作进程。酸洗/IPC的唯一大数据是工人创建的新数据

#!/usr/bin/python2.7

import numpy as np, sys
from multiprocessing import Pool

_mproc_data = {}  # global storage for objects during multiprocessing.

class BigData:
    def __init__(self, size):
        self.blah = np.random.uniform(0, 1, size=size)

    def do_chunk(self, k, xi, yi):
        # do the work and return an array of the same shape as xi, yi
        zi = k*np.ones_like(xi)
        return zi

    def do_all_work(self, xi, yi, num_proc):
        global _mproc_data
        mp_key = str(id(self))
        _mproc_data['bd'+mp_key] = self # BigData
        _mproc_data['xi'+mp_key] = xi
        _mproc_data['yi'+mp_key] = yi
        pool = Pool(processes=num_proc)
        # processes have now inherited the global variabele; clean up in the parent process
        for v in ['bd', 'xi', 'yi']:
            del _mproc_data[v+mp_key]

        # setup indices for the worker processes (placeholder)
        n_chunks = 45
        n = len(xi)
        chunk_len = n//n_chunks
        i1list = np.arange(0,n,chunk_len)
        i2list = i1list + chunk_len
        i2list[-1] = n
        klist = range(n_chunks) # placeholder

        procs = []
        for i in range(n_chunks):
            p = pool.apply_async( _do_chunk_wrapper, (mp_key, i1list[i], i2list[i], klist[i]) )
            sys.stderr.write(".")
            procs.append(p)
        sys.stderr.write("\n")

        # allocate space for combined results
        zi = np.zeros_like(xi)

        # get data from workers and finish  
        for i, p in enumerate(procs):
            zi[i1list[i]:i2list[i]] = p.get(timeout=30) # timeout allows ctrl-C handling

        pool.close()
        pool.join()

        return zi

def _do_chunk_wrapper(key, i1, i2, k):
    """All arguments are small objects."""
    global _mproc_data
    bd = _mproc_data['bd'+key]
    xi = _mproc_data['xi'+key][i1:i2]
    yi = _mproc_data['yi'+key][i1:i2]
    return bd.do_chunk(k, xi, yi)


if __name__ == "__main__":
    xi, yi = np.linspace(1, 100, 100001), np.linspace(1, 100, 100001)
    bd = BigData(int(1e7))
    bd.do_all_work(xi, yi, 4)
下面是速度测试的结果(同样,2个内核,4个线程),它改变了工作进程的数量和块中的内存量(数组片的
xi
yi
zi
的总字节数)。这些数字是以“每秒一百万个结果值”为单位的,但这对于比较来说并不重要。“1进程”的行是使用完整的输入数据直接调用
do_chunk
,而不使用任何子进程

#过程125K 250K 500K 1000K无限
1                                      0.82 
2       4.28    1.96    1.3     1.31 
3       2.69    1.06    1.06    1.07 
4       2.17    1.27    1.23    1.28 

内存中数据大小的影响非常显著。CPU有3 MB共享三级缓存,每个核心加上256 KB二级缓存。请注意,计算还需要访问
BigData
对象的几MB内部数据。因此,我们从中了解到,进行这种速度测试是有用的。对于这个程序,2个进程最快,其次是4个,3个进程最慢。

尽量减少进程间的通信。 在
多处理
模块中,所有(单台计算机)进程间通信通过队列完成。通过队列传递的对象 它们是腌制的。因此,尝试通过队列发送更少和/或更小的对象

  • 不要通过队列发送
    BigData
    的实例
    self
    。它相当大,并且随着
    self
    中数据量的增加而增大:

    In [6]: import pickle
    In [14]: len(pickle.dumps(BigData(50)))
    Out[14]: 1052187
    
    每一个 时间>代码>池。Apple yasyc(O-DojCujkWraseR,(自我,K,席,彝))< /Cord>被称为
    self
    在主进程中酸洗,在辅助进程中取消酸洗。这个
    len的大小(pickle.dumps(BigData(N))
    增加
    N
    增加

  • 让数据从全局变量中读取。在Linux上,您可以利用写时拷贝。作为:

    在fork()之后,父级和子级处于等效状态。将父级的整个内存复制到RAM中的另一个位置是愚蠢的。这就是“写上拷贝”原则的由来。只要子对象不改变其内存状态,它实际上就会访问父对象的内存。只有在修改后,相应的位和段才会复制到子级的内存空间中

    因此,您可以避免通过队列传递
    BigData
    的实例 简单地说
    #!/usr/bin/python2.7
    
    import numpy as np, sys
    from multiprocessing import Pool
    
    _mproc_data = {}  # global storage for objects during multiprocessing.
    
    class BigData:
        def __init__(self, size):
            self.blah = np.random.uniform(0, 1, size=size)
    
        def do_chunk(self, k, xi, yi):
            # do the work and return an array of the same shape as xi, yi
            zi = k*np.ones_like(xi)
            return zi
    
        def do_all_work(self, xi, yi, num_proc):
            global _mproc_data
            mp_key = str(id(self))
            _mproc_data['bd'+mp_key] = self # BigData
            _mproc_data['xi'+mp_key] = xi
            _mproc_data['yi'+mp_key] = yi
            pool = Pool(processes=num_proc)
            # processes have now inherited the global variabele; clean up in the parent process
            for v in ['bd', 'xi', 'yi']:
                del _mproc_data[v+mp_key]
    
            # setup indices for the worker processes (placeholder)
            n_chunks = 45
            n = len(xi)
            chunk_len = n//n_chunks
            i1list = np.arange(0,n,chunk_len)
            i2list = i1list + chunk_len
            i2list[-1] = n
            klist = range(n_chunks) # placeholder
    
            procs = []
            for i in range(n_chunks):
                p = pool.apply_async( _do_chunk_wrapper, (mp_key, i1list[i], i2list[i], klist[i]) )
                sys.stderr.write(".")
                procs.append(p)
            sys.stderr.write("\n")
    
            # allocate space for combined results
            zi = np.zeros_like(xi)
    
            # get data from workers and finish  
            for i, p in enumerate(procs):
                zi[i1list[i]:i2list[i]] = p.get(timeout=30) # timeout allows ctrl-C handling
    
            pool.close()
            pool.join()
    
            return zi
    
    def _do_chunk_wrapper(key, i1, i2, k):
        """All arguments are small objects."""
        global _mproc_data
        bd = _mproc_data['bd'+key]
        xi = _mproc_data['xi'+key][i1:i2]
        yi = _mproc_data['yi'+key][i1:i2]
        return bd.do_chunk(k, xi, yi)
    
    
    if __name__ == "__main__":
        xi, yi = np.linspace(1, 100, 100001), np.linspace(1, 100, 100001)
        bd = BigData(int(1e7))
        bd.do_all_work(xi, yi, 4)
    
    In [6]: import pickle
    In [14]: len(pickle.dumps(BigData(50)))
    Out[14]: 1052187
    
    p = pool.apply_async(_do_chunk_wrapper, (k_start, k_end, xi, yi))
    
    import math
    import numpy as np
    import time
    import sys
    import multiprocessing as mp
    import scipy.interpolate as interpolate
    
    _tm=0
    def stopwatch(msg=''):
        tm = time.time()
        global _tm
        if _tm==0: _tm = tm; return
        print("%s: %.2f seconds" % (msg, tm-_tm))
        _tm = tm
    
    class BigData:
        def __init__(self, n):
            z = np.random.uniform(size=n*n*n).reshape((n,n,n))
            self.ff = []
            for i in range(n):
                f = interpolate.RectBivariateSpline(
                    np.arange(n), np.arange(n), z[i], kx=1, ky=1)
                self.ff.append(f)
            self.n = n
    
        def do_chunk(self, k, xi, yi):
            n = self.n
            s = np.sum(np.exp(self.ff[k].ev(xi, yi)))
            sys.stderr.write(".")
            return s
    
        def do_chunk_of_chunks(self, k_start, k_end, xi, yi):
            s = sum(np.sum(np.exp(self.ff[k].ev(xi, yi)))
                        for k in range(k_start, k_end))
            sys.stderr.write(".")
            return s
    
        def do_multi(self, numproc, xi, yi):
            procs = []
            pool = mp.Pool(numproc)
            stopwatch('\nPool setup')
            ks = list(map(int, np.linspace(0, self.n, numproc+1)))
            for i in range(len(ks)-1):
                k_start, k_end = ks[i:i+2]
                p = pool.apply_async(_do_chunk_wrapper, (k_start, k_end, xi, yi))
                procs.append(p)
            stopwatch('Jobs queued (%d processes)' % numproc)
            total = 0.0
            for k, p in enumerate(procs):
                total += np.sum(p.get(timeout=30)) # timeout allows ctrl-C interrupt
                if k == 0: stopwatch("\nFirst get() done")
            print(total)
            stopwatch('Jobs done')
            pool.close()
            pool.join()
            return total
    
        def do_single(self, xi, yi):
            total = 0.0
            for k in range(self.n):
                total += self.do_chunk(k, xi, yi)
            stopwatch('\nAll in single process')
            return total
    
    def _do_chunk_wrapper(k_start, k_end, xi, yi): 
        return bd.do_chunk_of_chunks(k_start, k_end, xi, yi)        
    
    if __name__ == "__main__":
        stopwatch()
        n = 50
        bd = BigData(n)
        m = 1000*1000
        xi, yi = np.random.uniform(0, n, size=m*2).reshape((2,m))
        stopwatch('Initialized')
        bd.do_multi(2, xi, yi)
        bd.do_multi(3, xi, yi)
        bd.do_single(xi, yi)
    
    Initialized: 0.15 seconds
    
    Pool setup: 0.06 seconds
    Jobs queued (2 processes): 0.00 seconds
    
    First get() done: 6.56 seconds
    83963796.0404
    Jobs done: 0.55 seconds
    ..
    Pool setup: 0.08 seconds
    Jobs queued (3 processes): 0.00 seconds
    
    First get() done: 5.19 seconds
    83963796.0404
    Jobs done: 1.57 seconds
    ...
    All in single process: 12.13 seconds
    
    Initialized: 0.10 seconds
    Pool setup: 0.03 seconds
    Jobs queued (2 processes): 0.00 seconds
    
    First get() done: 10.47 seconds
    Jobs done: 0.00 seconds
    ..................................................
    Pool setup: 0.12 seconds
    Jobs queued (3 processes): 0.00 seconds
    
    First get() done: 9.21 seconds
    Jobs done: 0.00 seconds
    ..................................................
    All in single process: 12.12 seconds