微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

如何解决在函数内调用多处理池非常慢

我正在尝试使用 pathos 在函数内触发多处理。然而,我注意到一个奇怪的行为,不知道为什么:

import spacy
from pathos.multiprocessing import Processpool as Pool


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

# Run this in jupyter:
%%time

pool = Pool(3)
r = pool.map(worker,texts)

输出

cpu times: user 6.6 ms,sys: 26.5 ms,total: 33.1 ms
Wall time: 141 ms

到目前为止一切顺利......现在我定义了相同的精确计算,但是来自一个函数

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

# Run this in jupyter:
%%time 

r = out_worker(texts,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)

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,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,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

解决方法

代替 from pathos.multiprocessing import ProcessPool as Pool,我用过 from multiprocess import Pool,本质上是一样的。然后我尝试了一些替代方法。

所以:

from multiprocess import Pool

对于“通常”情况产生 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 并将全局变量的行为切换为 recurse 时(这就是 cloudpickle 的酸洗方式),然后在所有三个尝试中都很慢(“通常”方式包括)。

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

结论:如果可行,请使用 pathos.pools.ThreadPoolmultiprocess.dummy.Pool。否则,请确保以不序列化全局变量的方式运行。

dill 中有一个有用的工具,您可以使用它来查看正在序列化的内容。如果包含 dill.detect.trace(True),则 dill 会为它正在序列化的对象吐出一堆代码,因为它递归地腌制对象及其依赖项。您必须查看 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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。