繁体   English   中英

Python 中的多处理:共享数组的池和进程

[英]Multiprocessing in Python: Pool and Process with shared array

在浏览了许多关于相同/相似主题的讨论之后,我仍然无法解决我的问题,因此我想在下面发布它。

以下是我想要并行化的 MWE,即求解一组独立的线性方程(nI+mat)x=y ,参数化为n=0,1,2 ,固定 arrays maty 请注意,arrays 被声明为global的,希望它们可以被不同的进程/池访问(见下文)。 但我认为它不起作用,这是问题的核心:如何为不同的进程/池共享大 numpy arrays 以避免通信开销?

import numpy as np 
import time 
import os


N = 2000
num = 3 
global mat 
mat = np.random.rand(N, N)
global y 
y = np.random.rand(N,1)

# Functions to be parallelized num of times
def fun(n): 
    print(f"{n}-th job is run on child {os.getpid()} of parent {os.getppid()}")
    newmat = n * np.eye(N) + mat
    
    return np.linalg.solve(newmat, y)

# Approach 1: no parallel
def main():
    start_time = time.time()
    res = []
    for i in range(num):
        res.append(fun(i))
    print(f"Approach 1: Time elapsed = {time.time()-start_time} sec")
    return res
main()

我尝试了以下三种方法来并行化它: PoolProcessProcess with Arraynumpy.frombuffer 见下文。

from multiprocessing import Process, set_start_method, Queue, Pool, cpu_count, Array, RawArray
set_start_method('fork') 

# Approach 2: with pool 
def main2():
    start_time = time.time()
    pool = Pool(cpu_count())
    res = pool.map(fun, range(num))
    print(f"Approach 2: Time elapsed = {time.time()-start_time} sec")
    pool.close()
    return res    
main2()

# Approach 3: with process
def fun2(i, output):
    output.put(fun(i))

def main3():
    start_time = time.time()
    output = Queue()
    processes = [Process(target=fun2, args=(i, output)) for i in range(num)]
    # Run processes
    for p in processes:
        p.start()

    # Exit the completed processes
    for p in processes:
        p.join()            

    res = [output.get() for _ in processes]
    
    print(f"Approach 3: Time elapsed = {time.time()-start_time} sec")
    
    return res       
main3()

# Approach 4: with process with Array, numpy.frombuffer, 
def fun3(n, output, mat, y):
    print(f"{n}-th job is run on child {os.getpid()} of parent {os.getppid()}")
    mat2 = np.frombuffer(mat.get_obj())
    newmat = n * np.eye(N) + mat2.reshape((N, N))
    output.put(np.linalg.solve(newmat, y))

def main4():
    mat2 = Array('d', mat.flatten())
    y2 = Array('d', y)
    start_time = time.time()
    output = Queue()
    processes = [Process(target=fun3, args=(i, output, mat2, y2)) for i in range(num)]

    # Run processes
    for p in processes:
        p.start()

    # Exit the completed processes
    for p in processes:
        p.join()            

    res = [output.get() for _ in processes]
    
    print(f"Approach 4: Time elapsed = {time.time()-start_time} sec")
    
    return res
main4()

这些方法都不起作用,我得到了

0-th job is run on child 8818 of parent 3421
1-th job is run on child 8818 of parent 3421
2-th job is run on child 8818 of parent 3421
Approach 1: Time elapsed = 0.2891273498535156 sec
0-th job is run on child 8819 of parent 8818
1-th job is run on child 8820 of parent 8818
2-th job is run on child 8821 of parent 8818
Approach 2: Time elapsed = 3.6278929710388184 sec
0-th job is run on child 8832 of parent 8818
1-th job is run on child 8833 of parent 8818
2-th job is run on child 8834 of parent 8818
Approach 3: Time elapsed = 4.243804931640625 sec
0-th job is run on child 8843 of parent 8818
1-th job is run on child 8844 of parent 8818
2-th job is run on child 8845 of parent 8818
Approach 4: Time elapsed = 4.745251893997192 sec

这总结了我迄今为止看到的所有方法。 我知道多处理中有一个SharedMemory ,它不适用于 python 3.7.2。 如果这可以解决问题,我会很高兴看到它是如何工作的。

真的感谢任何人通读整篇文章,并感谢任何帮助。 如果它很重要,我使用的是带有 Apple M1 芯片的 Mac,macOS Monterey。

更新 1:根据@AKX 的观点,我删除了print(n-th job)行,并使N=10000 ,结果是

Approach 1: Time elapsed = 23.812573194503784 sec
Approach 2: Time elapsed = 126.91087889671326 sec

对于方法 3,它需要大约 5 分钟,我必须将其切断。 所以对于大 N 来说,时间开销是相当大的。

要回答这个问题,我们需要谈谈multiprocessing是如何工作的。

在类 UNIX 平台上, multiprocessing使用fork系统调用。 这将创建一个几乎完美的父进程副本,为您复制maty 在这种情况下,共享它们没有多大意义,因为现代类 UNIX 操作系统倾向于在可能的情况下对 memory 页面使用写时复制。

在 macOS 和 ms-windows 等平台上,它会启动一个新的 Python 实例并将您的代码默认导入其中。 虽然你可以在 macOS 上使用fork 所以它会在每个实例中重新创建maty

如果你想在这种情况下共享数据, multiprocessing有几种机制,比如ArrayManager 后者应该可以用一些胶水共享 numpy arrays 。 而且您必须记住,使用它们会产生一些开销; 他们的用例是针对共享数据的修改,所以它有处理这个问题的机制。 在你的情况下你不需要。

由于您使用的是fork ,我认为共享数据不会快得多。 如果数据大于 memory 的页面,则操作系统应该使用写时复制共享,因此它也不会为您节省 memory。

作为替代方案,您可以将maty写入父进程中的文件,并在工作进程中读取它们。

或者您可以使用只读mmap 但在这种情况下,您仍然需要将其转换为 numpy 数组。

np.linalg.solve应该已经在 LAPACK 中实现了并行执行function。 事实上,在我的(Linux + Windows)机器上就是这种情况。 实际上,它调用了dtrsmdlaswp等 LAPACK 函数,以及在 BLAS 库中实现的主要计算 function、 dgemm 只要您使用快速 BLAS 实现,最后一个 function 应该花费 >90% 的时间,并且经过高度优化和并行化。 Numpy 在大多数系统上默认使用 OpenBLAS,这非常好(和并行)。 英特尔 MKL 也是支持 LAPACK 的好选择(在英特尔硬件上当然更好)。 如果您的机器上的计算不是并行的,这是一个问题,您应该检查您的 BLAS 实现,因为它可能非常低效。

事情是并行化已经并行的代码使其变慢,因为运行比可用内核更多的线程会给 OS 调度程序带来很大压力,并且 BLAS 函数没有针对这种情况进行优化。 如果您真的想并行化 function 您需要配置 BLAS 以使用 1 个线程,但这可能比让 BLAS 进行并行化效率低(主要是因为 Python 由于酸洗全局解释器锁)。 这是您的并行方法比第一种顺序方法慢得多的一个原因。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM