Python 在函数中调用多处理池非常慢

Python 在函数中调用多处理池非常慢,python,multiprocessing,spacy,pathos,Python,Multiprocessing,Spacy,Pathos,我试图使用pathos来触发函数中的多处理。然而,我注意到一种奇怪的行为,不知道为什么: 导入空间 从paths.multiprocessing导入ProcessPool作为池 nlp=spacy.load(“核心新闻”) def预工作者(文本,nlp): 返回[w.lemma u]表示nlp中的w(文本)] worker=lambda text:preworker(文本,nlp) texts=[“我对西班牙感兴趣”]*10 #在jupyter中运行此命令: %%时间 池=池(3) r=pool

我试图使用pathos来触发函数中的多处理。然而,我注意到一种奇怪的行为,不知道为什么:

导入空间
从paths.multiprocessing导入ProcessPool作为池
nlp=spacy.load(“核心新闻”)
def预工作者(文本,nlp):
返回[w.lemma u]表示nlp中的w(文本)]
worker=lambda text:preworker(文本,nlp)
texts=[“我对西班牙感兴趣”]*10
#在jupyter中运行此命令:
%%时间
池=池(3)
r=pool.map(工作者、文本)
输出是

CPU times: user 6.6 ms, sys: 26.5 ms, total: 33.1 ms
Wall time: 141 ms
到目前为止还不错。。。现在我定义了相同的精确计算,但来自一个函数:

def out_worker(文本,nlp):
worker=lambda text:preworker(文本,nlp)
池=池(3)
返回池.map(工作者、文本)
#在jupyter中运行此命令:
%%时间
r=外部工作者(文本,nlp)
现在的输出是

CPU times: user 10.2 s, sys: 591 ms, total: 10.8 s
Wall time: 13.4 s
为什么会有如此大的差异?我的假设是,在第二种情况下,会向每个作业发送nlp对象的副本,尽管我不知道为什么

另外,如何从函数中正确调用此多处理?

谢谢


编辑:

对于问题的再现性,下面是一个Python脚本,它显示了这种情况:

import spacy
from pathos.multiprocessing import ProcessPool as Pool
import time

# Install with python -m spacy download es_core_news_sm
nlp = spacy.load("es_core_news_sm")

def preworker(text, nlp):
    return [w.lemma_ for w in nlp(text)]

worker = lambda text: preworker(text, nlp)

texts = ["Este es un texto muy interesante en español"] * 10

st = time.time()
pool = Pool(3)
r = pool.map(worker, texts)
print(f"Usual pool took {time.time()-st:.3f} seconds")

def out_worker(texts, nlp):
    worker = lambda text: preworker(text, nlp)
    pool = Pool(3)
    return pool.map(worker, texts)

st = time.time()
r = out_worker(texts, nlp)
print(f"Pool within a function took {time.time()-st:.3f} seconds")

def out_worker2(texts, nlp, pool):     
    worker = lambda text: preworker(text, nlp)     
    return pool.map(worker, texts)

st = time.time()
pool = Pool(3) 
r = out_worker2(texts, nlp, pool)
print(f"Pool passed to a function took {time.time()-st:.3f} seconds")
在我的例子中,输出如下:

Usual pool took 0.219 seconds
Pool within a function took 8.164 seconds
Pool passed to a function took 8.265 seconds
spacy nlp对象相当重(几MB)。我的spacy版本是3.0.3,而不是
从pathos.multiprocessing导入ProcessPool作为池
,我使用 来自多进程导入池的
,本质上是相同的。然后我尝试了一些替代方法

因此:

对于“普通”情况,产生
0.1s
,对于其他两种情况产生
12.5s

然而:

from multiprocess import Pool
import dill 
dill.settings['recurse'] = True
三种情况下的产量均
12.5s

最后:

from multiprocess.dummy import Pool
三种情况下的收益率均
0.1s

这告诉我这肯定是一个序列化问题,而
globals
的序列化是速度的关键

在第一种情况下,默认的
dill
行为是尽可能避免通过
globals
递归。它能够以“通常”的方式成功地实现这一点,但不能用于函数内的其他两个调用

当我第一次导入
dill
并将globals的行为切换到
recurse
(这就是
cloudpickle
进行酸洗的方式),那么这三次尝试都很慢(包括“通常”的方式)

最后,如果我使用
multiprocess.dummy
,从而使用
ThreadPool
——它不需要序列化全局变量,您可以看到它在所有情况下都很快

结论:如果可行,使用
pathos.Pool.ThreadPool
多进程.dummy.Pool
。否则,请确保您的运行方式不会序列化全局变量

dill
中有一个有用的工具,可以用来查看序列化的内容。如果包含
dill.detect.trace(True)
,那么dill会为它正在序列化的对象吐出一堆代码,因为它递归地pickle对象及其依赖项。您必须查看
dill
源代码以匹配键(例如
F1
是特定类型的函数对象,
D1
是特定类型的字典)。您可以看到不同的方法如何序列化不同的底层对象。不幸的是,我没有一个剖析器在上面,所以你不能立即看到速度打击在哪里,但是你可以看到它采取的不同策略

我会尽量避免序列化
nlp
对象,或是导致速度减慢的任何东西(可能是
nlp
对象)

例如,您可以这样做,而不是在函数中传递nlp对象:

import spacy
from multiprocess import Pool
import time

# Install with python -m spacy download es_core_news_sm
nlp = spacy.load("es_core_news_sm")

def preworker(text, nlp):
    return [w.lemma_ for w in nlp(text)]

worker = lambda text: preworker(text, nlp)

texts = ["Este es un texto muy interesante en espanol"] * 10

st = time.time()
pool = Pool(3)
r = pool.map(worker, texts)
pool.close(); pool.join()
print("Usual pool took {0:.3f} seconds".format(time.time()-st))

def out_worker(texts):
    worker = lambda text: preworker(text, nlp)
    pool = Pool(3)
    res = pool.map(worker, texts)
    pool.close(); pool.join()
    return res

st = time.time()
r = out_worker(texts)
print("Pool within a function took {0:.3f} seconds".format(time.time()-st))

通过引用查找而不是显式地通过函数参数传递
nlp
,两种情况下的速度都是
0.1s

我是
pathos
的作者。如果在函数外部构建池,然后将池(或映射)传递到函数中(作为参数),该怎么办?我通常就是这么做的。或者。@MikeMcKerns我刚刚做了:
python def out_worker2(text,nlp,pool):worker=lambda text:preworker(text,nlp)return pool.map(worker,text)%%time pool=pool(3)r=out_worker2(text,nlp,pool)
时间是10.4sIt肯定是每种情况下序列化的问题。您能否在Jupyter之外(即在标准python解释器中或仅在文件中)尝试您的示例?由于笔记本电脑中使用了电池,Jupyter对globals进行了一些修补。如果有一个完整的例子来说明你所看到的行为也会很好。@MikeMcKerns我刚刚用python脚本编辑了我的问题。我认为这个脚本非常独立(即使它使用spacy),这是一个很重的NLP模型对象
import spacy
from multiprocess import Pool
import time

# Install with python -m spacy download es_core_news_sm
nlp = spacy.load("es_core_news_sm")

def preworker(text, nlp):
    return [w.lemma_ for w in nlp(text)]

worker = lambda text: preworker(text, nlp)

texts = ["Este es un texto muy interesante en espanol"] * 10

st = time.time()
pool = Pool(3)
r = pool.map(worker, texts)
pool.close(); pool.join()
print("Usual pool took {0:.3f} seconds".format(time.time()-st))

def out_worker(texts):
    worker = lambda text: preworker(text, nlp)
    pool = Pool(3)
    res = pool.map(worker, texts)
    pool.close(); pool.join()
    return res

st = time.time()
r = out_worker(texts)
print("Pool within a function took {0:.3f} seconds".format(time.time()-st))