我可以通过将函数应用于具有多处理功能的数据对象中的项来提高性能吗



免责声明:我在SO和文档上查看了大量的multiprocessing答案,要么这些问题真的很旧(自那以后,Python 3.X进行了大量改进(,要么没有找到明确的答案。如果我可能错过了一些相关的东西,一定要给我指明正确的方向。

我从一个简单的函数开始,我在文件夹模块中定义如下,因为我正在运行Jupyter Notebook,由于冲突,你似乎只能在导入的函数上运行multiprocessing

def f(a):
return a * 100

构建了一些测试数据并运行了一些测试:

from itertools import zip_longest
from multiprocessing import Process, Pool, Array, Queue
from time import time
from modules.test import *
li = [i for i in range(1000000)]

列表理解:真的很快

start = time()
tests = [f(i) for i in li]
print(f'Total time {time() - start} s')
>> Total time 0.154066801071167 s

此处SO示例的答案:11秒左右

start = time()
results = []
if __name__ == '__main__':
jobs = 4
size = len(li)
heads = list(range(size//jobs, size, size//jobs)) + [size]
tails = range(0,size,size//jobs)
pool = Pool(4)
for tail,head in zip(tails, heads):
r = pool.apply_async(f, args=(li[tail:head],))
results.append(r)
pool.close()
pool.join() # wait for the pool to be done
print(f'Total time {time() - start} s')
>>Total time 11.087551593780518 s

还有Process,我不知道它是否适用于上面的例子。我不熟悉multiprocessing,但我确实知道在创建新实例时会有一些开销,但随着数据的增长,应该可以证明开销是合理的。

我的问题是,以Python3.x中当前的性能,在运行与上述操作类似的操作时使用multiprocessing是否仍然相关,或者是否应该尝试。如果是,如何将它们应用于工作负载的并行化。

我阅读和理解的大多数例子都是用于网络抓取的,当一个接收信息的过程中有一段实际的空闲时间时,并行化是有意义的,但如果你正在运行列表或字典之类的计算,你该如何处理它。

您的示例执行不好的原因是您正在做两件完全不同的事情。

在您的列表理解中,您正在f映射到li的每个元素上。

在第二种情况下,您将li列表拆分为jobs块,然后将函数job的次数应用于这些块中的每一个。现在,在f中,n * 100取一个大约是原始列表大小四分之一的块,并将其乘以100,即,它使用序列重排序运算符,因此创建一个块大小100倍的新列表

>>> chunk = [1,2,3]
>>> chunk * 10
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
>>>

所以基本上,你是在把苹果比作桔子。

然而,多处理已经附带了一个开箱即用的映射实用程序。这里有一个更好的比较,一个名为foo.py:的脚本

import time
import multiprocessing as mp
def f(x):
return x * 100
if __name__ == '__main__':
data = list(range(1000000))
start = time.time()
[f(i) for i in data]
stop = time.time()
print(f"List comprehension took {stop - start} seconds")
start = time.time()
with mp.Pool(4) as pool:
result = pool.map(f, data)
stop = time.time()
print(f"Pool.map took {stop - start} seconds")

下面是一些实际的性能结果:

(py37) Juans-MBP:test_mp juan$ python foo.py
List comprehension took 0.14193987846374512 seconds
Pool.map took 0.2513458728790283 seconds
(py37) Juans-MBP:test_mp juan$

对于这个非常琐碎的函数,进程间通信的成本总是高于串行计算函数的成本。因此,您不会从多处理中看到任何收益。然而,一个不那么琐碎的函数可以从多处理中获益。

这里有一个微不足道的例子,在相乘之前我只睡了一微秒:

import time
import multiprocessing as mp
def f(x):
time.sleep(0.000001)
return x * 100
if __name__ == '__main__':
data = list(range(1000000))
start = time.time()
[f(i) for i in data]
stop = time.time()
print(f"List comprehension took {stop - start} seconds")
start = time.time()
with mp.Pool(4) as pool:
result = pool.map(f, data)
stop = time.time()
print(f"Pool.map took {stop - start} seconds")

现在,您可以看到与进程数量相称的收益:

(py37) Juans-MBP:test_mp juan$ python foo.py
List comprehension took 13.175776720046997 seconds
Pool.map took 3.1484851837158203 seconds

请注意,在我的机器上,一次乘法所需的时间比一微秒(大约10纳秒(少几个数量级:

>>> import timeit
>>> timeit.timeit('100*3', number=int(1e6))*1e-6
1.1292944999993892e-08

最新更新