【问题标题】:Python 3 Multiprocessing PoolPython 3 多处理池
【发布时间】:2018-04-20 02:41:38
【问题描述】:

我正在学习将池与多处理一起使用。我做了这个脚本作为练习。

谁能告诉我为什么使用普通的 for 循环比使用池花费的时间更少?

P.S:我的 CPU 有 2 个核心。

非常感谢。

from multiprocessing import Pool
from functools import reduce
import time

def one(n):
    a = n*n
    return a 

if __name__ == '__main__':
    l = list(range(1000))

    p = Pool()
    t = time.time()
    pol = p.map(one, l)
    result = reduce(lambda x,y: x+y, pol)
    print("Using Pool the result is: ", result, "Time: ", time.time() - t )
    p.close()
    p.join()

    def two(n):
        t = time.time()
        p_result = [] 

        for i in n:
            a = i*i 
            p_result.append(a)

        result = reduce(lambda x,y: x+y, p_result)
        print("Not using Pool the result is: ", result, "Time: ", time.time() - t)

    two(l)

使用 Pool 结果为: 332833500 时间: 0.14810872077941895

不使用 Pool 结果是: 332833500 时间: 0.0005018711090087891

【问题讨论】:

    标签: python-3.x multiprocessing pool


    【解决方案1】:

    我认为这里有几个原因,但我猜这主要与运行多个进程的开销有关,这主要与同步和通信有关,以及您的非并行化代码的编写效率更高。

    作为基础,以下是您未修改的代码在我的计算机上的运行方式:

    ('Using Pool the result is: ', 332833500, 'Time: ', 0.0009129047393798828)
    ('Not using Pool the result is: ', 332833500, 'Time: ', 0.000598907470703125)
    

    首先,我想通过使two() 函数的代码与并行化代码几乎相同来尝试平衡竞争环境。这是修改后的two()函数:

    def two(l):
        t = time.time()
    
        p_result = map(one, l)
    
        result = reduce(lambda x,y: x+y, p_result)
        print("Not using Pool the result is: ", result, "Time: ", time.time() - t)
    

    现在,在这种情况下,这实际上并没有太大的不同,但重要的是要马上看到两种情况都在做完全相同的事情。以下是此更改的示例输出:

    ('Using Pool the result is: ', 332833500, 'Time: ', 0.0009338855743408203)
    ('Not using Pool the result is: ', 332833500, 'Time: ', 0.0006031990051269531)
    

    我现在想说明的是,由于one() 函数的计算成本如此之低,因此进程间通信的开销超过了并行运行它的好处。我将修改one() 函数如下,以强制它进行大量额外计算。请注意,由于 two() 函数的更改,此更改将影响并行代码和单线程代码。

    def one(n):
        for i in range(100000):
            a = n*n
        return a
    

    for循环的原因是给每个进程一个存在的理由。当您拥有原始代码时,每个进程只需进行几次乘法运算,然后必须将结果列表发送回父进程,并等待获得一个新块。发送和等待比完成单个块花费的时间要长得多。通过添加这些额外的周期,它迫使每个块花费更长的时间,而不会改变进程间通信所需的时间,因此我们开始看到并行性得到了回报。以下是我对 one() 函数的更改运行代码时的结果:

    ('Using Pool the result is: ', 332833500, 'Time: ', 1.861448049545288)
    ('Not using Pool the result is: ', 332833500, 'Time: ', 3.444211959838867)
    

    所以你有它。您所需要的只是让您的子进程多做一些工作,它们会更值得您花时间。

    【讨论】:

    • 非常感谢 TallChuck,您的回答非常有启发性。
    【解决方案2】:

    当使用Pool时,python使用global interpreter lock在多个进程之间同步多个线程。也就是说,当一个线程正在运行时,所有其他线程都停止/等待。因此,您将体验到的是顺序执行,而不是并行执行。在您的示例中,即使您在pool 中的多个线程之间分配,它们也会由于全局解释器锁而按顺序运行。这也增加了很多调度开销。

    来自全局解释器锁上的python文档:

    CPython 解释器用来确保只有一个 线程一次执行 Python 字节码。这简化了 CPython 通过制作对象模型(包括关键的内置 dict 等类型对并发访问隐式安全。锁定 整个口译员使口译员更容易 多线程,以牺牲大部分并行性为代价 多处理器机器。

    因此,您实现的并不是真正的并行性。如果你需要在python中实现真正的多处理能力,你需要使用Processes,这将导致你使用Queues在进程之间交换数据。

    【讨论】:

    • 全局解释器锁仅在单个进程中有效。如果你在运行 Pool 的同时运行 ps,你会看到它实际上是在使用多个进程
    • 是的,你的论点是正确的。但根据我的个人经验,global interpreter lock 会影响使用线程而不是进程的时间。你怎么看?
    • 当然可以,但是提供的代码不使用线程,除非threading 库用于处理multiprocessing 库中的进程
    • 我明白了。感谢您提供详细信息。
    【解决方案3】:

    多处理包提供本地和远程并发,通过使用子进程而不是线程来有效地避开全局解释器锁。因此,多处理模块允许程序员充分利用给定机器上的多个处理器。

    在 Python 2.7.16 文档的 Process-based “threading” interface 章节中找到

    【讨论】:

    • 看起来你是从docs.python.org/2/library/multiprocessing.html 复制的,如果你这样做了,请说明出处并100% 明确文本不是你的。也更喜欢不写答案,这些答案只不过是场外资源的文本副本。将它们用作备份,而不是作为答案的核心。
    猜你喜欢
    • 2021-07-24
    • 2020-08-14
    • 2016-11-10
    • 2017-04-18
    • 1970-01-01
    • 2014-08-25
    • 2016-12-07
    • 2020-03-12
    • 2017-11-17
    相关资源
    最近更新 更多