multiprocessing.Pool 对比 multiprocessing.pool.ThreadPool
multiprocessing.Pool vs multiprocessing.pool.ThreadPool
下面是multiprocessing.Pool
vs multiprocessing.pool.ThreadPool
vs sequential version的一些测试,我想知道为什么multiprocessing.pool.ThreadPool
版本比sequential version慢?
multiprocessing.Pool
确实更快,因为它使用进程(即没有 GIL)并且 multiprocessing.pool.ThreadPool
使用线程(即有 GIL)尽管包的名称为 multiprocessing
?
import time
def test_1(job_list):
from multiprocessing import Pool
print('-' * 60)
print("Pool map")
start = time.time()
p = Pool(8)
s = sum(p.map(sum, job_list))
print('time:', time.time() - start)
def test_2(job_list):
print('-' * 60)
print("Sequential map")
start = time.time()
s = sum(map(sum, job_list))
print('time:', time.time() - start)
def test_3(job_list):
from multiprocessing.pool import ThreadPool
print('-' * 60)
print("ThreadPool map")
start = time.time()
p = ThreadPool(8)
s = sum(p.map(sum, job_list))
print('time:', time.time() - start)
if __name__ == '__main__':
job_list = [range(10000000)]*128
test_1(job_list)
test_2(job_list)
test_3(job_list)
输出:
------------------------------------------------------------
Pool map
time: 3.4112906455993652
------------------------------------------------------------
Sequential map
time: 23.626681804656982
------------------------------------------------------------
ThreadPool map
time: 76.83279991149902
您的任务纯粹是 CPU 绑定(在 I/O 上没有阻塞)并且没有使用任何手动释放 GIL 的扩展代码来执行大量 number-c运行 ching 不涉及 Python 级引用计数对象(例如 hashlib
哈希大输入、大数组 numpy
计算等)。因此,GIL 的定义阻止您从代码中提取 any 并行性;只有一个线程可以同时持有 GIL 并执行 Python 字节码,你会变慢,因为:
- 您必须启动所有这些线程
- 他们必须在他们之间交出 GIL 来模拟并行处理
- 您必须清理所有线程
简而言之,是的,ThreadPool
does what it says on the tin:它提供与 Pool
相同的 API,但由线程支持,而不是工作进程,因此没有避免 GIL 限制和开销.直到最近它才被直接记录下来;相反,它是由 multiprocessing.dummy
文档间接记录的,这些文档更明确地提供了 multiprocessing
API 但由线程支持,而不是进程(您将其用作 multiprocessing.dummy.Pool
,没有名称实际上包括单词“Thread”)。
我会注意到您的测试使 Pool
看起来比正常情况下更好。通常,Pool
对这样的任务(大量数据,相对于数据大小的计算很少)会表现不佳,因为序列化数据并将其发送到子进程的成本超过了并行化工作的微小收益.但是由于你的“大数据”是由 range
对象表示的(它们被廉价地序列化,作为对 range
class 的引用和重建它的参数),很少的数据是转移给工人和从工人那里转移。如果您使用真实数据(实现了 int
的 list
),Pool
带来的好处会急剧下降。例如,只需将 job_list
的定义更改为:
job_list = [[*range(10000000)]] * 128
在我的机器上 Pool
的时间(对于未修改的 Pool
情况需要 3.11 秒)跳到 8.11 秒。甚至这是一个谎言,因为 pickle
序列化代码识别一遍又一遍重复的相同 list
并只序列化内部 list
一次,然后快速重复它“先看那个 list
" 代码。我会告诉你使用什么:
job_list = [[*range(10000000)] for _ in range(128)]
做了 运行 时间,但我只是试图 运行 那行差点让我的机器崩溃(它需要 ~46 GB 的内存来创建 list
的list
s,并且该成本将在父进程中支付一次,然后在子进程中再次支付);可以这么说,Pool
会丢失非常严重,尤其是在数据只适合 RAM 一次而不适合两次的情况下。
下面是multiprocessing.Pool
vs multiprocessing.pool.ThreadPool
vs sequential version的一些测试,我想知道为什么multiprocessing.pool.ThreadPool
版本比sequential version慢?
multiprocessing.Pool
确实更快,因为它使用进程(即没有 GIL)并且 multiprocessing.pool.ThreadPool
使用线程(即有 GIL)尽管包的名称为 multiprocessing
?
import time
def test_1(job_list):
from multiprocessing import Pool
print('-' * 60)
print("Pool map")
start = time.time()
p = Pool(8)
s = sum(p.map(sum, job_list))
print('time:', time.time() - start)
def test_2(job_list):
print('-' * 60)
print("Sequential map")
start = time.time()
s = sum(map(sum, job_list))
print('time:', time.time() - start)
def test_3(job_list):
from multiprocessing.pool import ThreadPool
print('-' * 60)
print("ThreadPool map")
start = time.time()
p = ThreadPool(8)
s = sum(p.map(sum, job_list))
print('time:', time.time() - start)
if __name__ == '__main__':
job_list = [range(10000000)]*128
test_1(job_list)
test_2(job_list)
test_3(job_list)
输出:
------------------------------------------------------------
Pool map
time: 3.4112906455993652
------------------------------------------------------------
Sequential map
time: 23.626681804656982
------------------------------------------------------------
ThreadPool map
time: 76.83279991149902
您的任务纯粹是 CPU 绑定(在 I/O 上没有阻塞)并且没有使用任何手动释放 GIL 的扩展代码来执行大量 number-c运行 ching 不涉及 Python 级引用计数对象(例如 hashlib
哈希大输入、大数组 numpy
计算等)。因此,GIL 的定义阻止您从代码中提取 any 并行性;只有一个线程可以同时持有 GIL 并执行 Python 字节码,你会变慢,因为:
- 您必须启动所有这些线程
- 他们必须在他们之间交出 GIL 来模拟并行处理
- 您必须清理所有线程
简而言之,是的,ThreadPool
does what it says on the tin:它提供与 Pool
相同的 API,但由线程支持,而不是工作进程,因此没有避免 GIL 限制和开销.直到最近它才被直接记录下来;相反,它是由 multiprocessing.dummy
文档间接记录的,这些文档更明确地提供了 multiprocessing
API 但由线程支持,而不是进程(您将其用作 multiprocessing.dummy.Pool
,没有名称实际上包括单词“Thread”)。
我会注意到您的测试使 Pool
看起来比正常情况下更好。通常,Pool
对这样的任务(大量数据,相对于数据大小的计算很少)会表现不佳,因为序列化数据并将其发送到子进程的成本超过了并行化工作的微小收益.但是由于你的“大数据”是由 range
对象表示的(它们被廉价地序列化,作为对 range
class 的引用和重建它的参数),很少的数据是转移给工人和从工人那里转移。如果您使用真实数据(实现了 int
的 list
),Pool
带来的好处会急剧下降。例如,只需将 job_list
的定义更改为:
job_list = [[*range(10000000)]] * 128
在我的机器上 Pool
的时间(对于未修改的 Pool
情况需要 3.11 秒)跳到 8.11 秒。甚至这是一个谎言,因为 pickle
序列化代码识别一遍又一遍重复的相同 list
并只序列化内部 list
一次,然后快速重复它“先看那个 list
" 代码。我会告诉你使用什么:
job_list = [[*range(10000000)] for _ in range(128)]
做了 运行 时间,但我只是试图 运行 那行差点让我的机器崩溃(它需要 ~46 GB 的内存来创建 list
的list
s,并且该成本将在父进程中支付一次,然后在子进程中再次支付);可以这么说,Pool
会丢失非常严重,尤其是在数据只适合 RAM 一次而不适合两次的情况下。