Python中的多处理池 - 仅使用单个CPU

时间:2015-05-07 07:42:07

标签: python multiprocessing pool

原始问题

我正在尝试在Python中使用多处理池。这是我的代码:

def f(x):
    return x

def foo():
    p = multiprocessing.Pool()
    mapper = p.imap_unordered

    for x in xrange(1, 11):
        res = list(mapper(f,bar(x)))

xrangexrange(1, 6)一样小时,此代码使用所有CPU(我有8个CPU)。但是,当我将范围增加到xrange(1, 10)时。我观察到只有1个CPU以100%运行,而其余的只是空转。可能是什么原因?是因为,当我增加范围时,操作系统会因过热而关闭CPU吗?

如何解决此问题?

最小,完整,可验证的例子

为了复制我的问题,我创建了这个例子:它是一个从字符串问题生成的简单ngram。

#!/usr/bin/python

import time
import itertools
import threading
import multiprocessing
import random


def f(x):
    return x

def ngrams(input_tmp, n):
    input = input_tmp.split()

    if n > len(input):
        n = len(input)

    output = []
    for i in range(len(input)-n+1):
        output.append(input[i:i+n])
    return output 

def foo():

    p = multiprocessing.Pool()
    mapper = p.imap_unordered

    num = 100000000 #100
    rand_list = random.sample(xrange(100000000), num)

    rand_str = ' '.join(str(i) for i in rand_list)

    for n in xrange(1, 100):
        res = list(mapper(f, ngrams(rand_str, n)))


if __name__ == '__main__':
    start = time.time()
    foo()
    print 'Total time taken: '+str(time.time() - start)

num很小(例如num = 10000)时,我发现所有8个CPU都被使用了。但是,当num非常大时(例如,num = 100000000)。仅使用2个CPU,其余为空闲。这是我的问题。

警告:当num过大时,系统/虚拟机可能会崩溃。

2 个答案:

答案 0 :(得分:7)

首先,ngrams本身需要花费很多时间。当这种情况发生时,它显然只有一个核心。但即使完成(通过在ngrams之外移动mapper电话并在其之前和之后投放print非常容易进行测试),您还可以< em>仍仅使用一个核心。我得到1核100%,其他核心大约2%。

如果你在Python 3.4中尝试相同的东西,情况会有所不同 - 我仍然得到100%的1核心,但其他的是15-25%。

那么,发生了什么?好吧,在multiprocessing中,传递参数和返回值总是有些开销。在您的情况下,这种开销完全淹没了实际工作,这只是return x

以下是开销如何工作:主进程必须挑选值,然后将它们放在队列中,然后等待另一个队列上的值并取消它们。每个子进程在第一个队列上等待,取消排序值,执行任何操作,修补值,并将它们放在另一个队列中。必须同步对队列的访问(在大多数非Windows平台上通过POSIX信号量,我认为在Windows上是NT内核互斥锁。)

据我所知,您的流程花费了99%以上的时间等待队列或阅读或写作。

这并不是意外,因为您需要处理大量数据,除了对这些数据进行酸洗和取消排序之外,根本不需要计算。

如果您查看CPython 2.7SimpleQueue的来源,则会在持有锁的情况下进行酸洗和去除。因此,几乎所有的后台进程都会在锁定时发生,这意味着它们最终都会在一个核心上进行序列化。

但是在CPython 3.4中,在锁定之外发生了酸洗和去除。而且显然足以使用15-25%的核心。 (我相信这种变化发生在3.2,但是我懒得跟踪它。)

尽管如此,即使在3.4上,你也要花费更多时间等待访问队列而不是做任何事情,甚至是multiprocessing开销。这就是为什么核心只能达到25%。

当然,您在开销上花费的时间比实际工作多了几个数量级,这使得这不是一个很好的测试,除非您尝试测试可以获得的最大吞吐量。您机器上的特定multiprocessing实施或其他内容。

一些观察结果:

  • 在您的真实代码中,如果您可以找到批量处理较大任务的方法(明确地 - 仅仅依靠chunksize=1000或类似的帮助),这可能会解决您的大多数问题
  • 如果您的巨型阵列(或其他)从未实际更改过,您可以在池初始化程序中传递它,而不是在每个任务中传递它,这几乎可以消除问题。
  • 如果它确实发生了变化,但只是来自主要流程方面,则可能值得共享而不是传递数据。
  • 如果你需要从子进程中改变它,看看是否有分区数据的方法,这样每个任务都可以拥有一个没有争用的分片。
  • 即使您需要使用显式锁定的完全竞争的共享内存,它仍然仍然比传递这么大的东西更好。
  • 可能值得从PyPI(或升级到Python 3.x)获取3.2+版本的multiprocessing或第三方multiprocessing库之一的后端,只是为了移动腌出来的锁。

答案 1 :(得分:2)

问题是你的f()函数(在不同的进程上运行的函数)没有做任何特殊的事情,因此它没有给CPU加载。

另一方面,

ngrams()正在做一些重的&#34;计算,但是你在主进程上调用此函数,而不是在池中。

为了使事情更清楚,请考虑这段代码......

for n in xrange(1, 100):
    res = list(mapper(f, ngrams(rand_str, n)))

......等同于:

for n in xrange(1, 100):
    arg = ngrams(rand_str, n)
    res = list(mapper(f, arg))

以下是在主进程上执行的CPU密集型操作:

num = 100000000
rand_list = random.sample(xrange(100000000), num)

您应该更改代码,以便在池中调用sample()ngrams(),或者更改f()以便它执行CPU密集型操作,并且您可以看到所有CPU的负载很高。