Python多处理:只有一个进程正在运行

时间:2014-09-13 17:52:21

标签: python performance multiprocessing

我正在尝试使用Python多处理模块生成多个并行进程。基本上,我做了像

这样的事情
pool = Pool(30)
results = [pool.apply_async(foo, (trainData, featureVector, terms, selLabel)) for selLabel in selLabels]
for r in results:
    tmp = r.get()
    modelFiles[tmp[0]] = tmp[1]

产生了30个进程,但是,似乎大多数进程已进入休眠状态,而实际只有一个进程正在运行。以下是我从ps获得的信息:

PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND

31267 74.6  2.4 7125412 6360080 pts/1 Sl+  13:06  24:25  \_ python2.6 /home/PerlModules/Python/DoOVA.py

31427 27.4  2.3 6528532 6120904 pts/1 R+   13:20   5:18      \_ python2.6 /home/PerlModules/Python/DoOVA.py

31428  0.0  1.3 4024724 3617016 pts/1 S+   13:20   0:00      \_ python2.6 /home/PerlModules/Python/DoOVA.py

31429  0.0  1.3 4024724 3617016 pts/1 S+   13:20   0:00      \_ python2.6 /home/PerlModules/Python/DoOVA.py

31430  0.0  1.3 4024724 3617016 pts/1 S+   13:20   0:00      \_ python2.6 /home/PerlModules/Python/DoOVA.py

DoOVA.py是我正在运行的脚本。他们中的大多数都具有状态S+

有人能给我一些关于问题的线索吗?我知道输入争论featureVector的大小非常大,比如大约300MB。那会是个问题吗?我运行的机器有几TB的内存。

foo做了类似的事情:

def foo(trainData, featureVector, terms, selLabel, penalty):
    outputFile = 'train_'+selLabel+'.dat'
    annotation = dict()
    for id in trainData:
        if trainData[id] == selLabel:
            annotation[id] = '1'
        else:
            annotation[id] = '-1'
    try:
        os.mkdir(selLabel)
        os.chdir(selLabel)
    except OSError:
        os.chdir(selLabel)
    ###Some more functions, which involves a command line call through call from subprocess module
    os.chdir('../')
    return (selLabel, 'SVM_' + selLabel + '.model')

所有其他输入参数的大小都很小。机器至少有100 cpus。在每次运行中,即使在创建任何目录之前,脚本也需要很长时间,尽管在os.mkdir()之前foo中没有发生重大计算。

1 个答案:

答案 0 :(得分:13)

由于评论表明您希望featureVector使用initializerinitargs参数传递给Pool。在Unix类型的系统上,这将导致大量的性能提升(即使selLabels中只有1项),因为该值将使用os.fork基本上免费传递给子进程。否则,每次调用foo时,featureVector将被父进程腌制,通过管道传递并由子进程取消激活。这将花费很长时间,并且基本上将序列化所有子进程,因为他们将等待父进程为每个调用逐个发送并发送featureVector的副本。

由于我上面谈到的内容存在一些混淆,所以在这里对代码中发生的内容进行了一些更长的解释:

创建Pool对象时,会立即创建30个工作进程,主进程的所有子进程都会创建Pool对象。为了与每个子进程进行通信,创建了一个管道。此管道允许父进程和子进程之间的双向通信。父级使用管道来指示子进程执行的操作,子级使用管道通知父级任何操作的结果。

当您第一次调用pool.apply_async时,父进程通过管道发送命令,指示子进程使用提供的参数执行foo函数。由于其中一个论点是巨大的,300MB,这最终需要很长时间。父进程必须pickle对象。这会将对象(及其引用的所有内容)转换为可通过管道发送的字节流。

由于管道只能容纳大约64k(Linux默认值),并且您发送的内容远不止这些,因此可以有效地同步父进程和其中一个子进程。父进程只能以子进程可以接收和取消它们的速度发送参数,并且子进程只能像父进程一样快地接收参数并发送它们。虽然这是在进行所有其他子进程必须等待。父进程一次只能向一个子进程发送命令。

父进程完成第一次调用foo的所有参数后,可以继续发送命令再次呼叫foo。在此之后不久,一旦子进程收到所有参数,孩子就会调用foo。 (这就是为什么在创建任何目录之前需要很长时间,甚至在调用foo之前需要很长时间。)在foo返回后,子进程将等待父进程发送另一个命令。如果foo本身需要足够短的时间来执行,那么接收第一个调用foo命令的子进程也可能会收到第二个调用{{1}的命令。 }}

除非foo本身需要花费很长时间才能执行,只要比通过管道发送foo所花费的时间长或长,否则您将被有效地限制为仅执行一个子进程在时间。父进程将尝试命令子进程尽可能快地调用featureVector,但由于foo如此之大,它只能以非常慢的速率执行此操作。完成将命令发送到一个进程以调用featureVector后,它命令调用foo的先前进程很久以前就已经完成了foo的调用。运行子进程之间几乎没有重叠。

为了解决代码中的性能问题,您需要执行以下操作:

foo

此代码也会使用def child_initialize(_trainData, _featureVector, _terms): global trainData, featureVector, terms trainData = _trainData featureVector = _featureVector terms = _terms def foo(selLabel): ... pool = Pool(30, initialize = child_initialize, initargs = (trainData, featureVector, terms)) results = [pool.apply_async(foo, (selLabel,)) for selLabel in selLabels] trainData假设他们不会改变。

虽然这应该会带来巨大的性能提升,并允许子进程并行运行,但这并不一定意味着子进程将在term处于可运行状态中显示出来更经常的。您的示例initargs函数似乎将花费大部分时间等待"命令行调用"完成。