我正在使用concurrent.futures.ThreadPoolExecutor来查看我是否可以从我的四核处理器(具有8个逻辑核心)中挤出更多工作。所以我写了下面的代码:
from concurrent import futures
def square(n):
return n**2
def threadWorker(t):
n, d = t
if n not in d:
d[n] = square(n)
def master(n, numthreads):
d = {}
with futures.ThreadPoolExecutor(max_workers=numthreads) as e:
for i in e.map(threadWorker, ((i, d) for i in range(n))):
pass # done so that it actually fetches each result. threadWorker has its own side-effects on d
return len(d)
if __name__ == "__main__":
print('starting')
print(master(10**6, 6))
print('done')
有趣的是,在for循环中编写相同的功能需要大约一秒钟时间:
>>> d = {}
>>> for i in range(10**6):
... if i not in d: d[i] = i**2
...虽然线程池代码需要10秒以上。现在我知道它使用至少4个线程,因为我看到每个核心上的处理器负载。但即使使用共享内存(我可以理解为什么进程可能需要一段时间,由于内存复制),我觉得运行时的这种差异太大了。
有没有人知道为什么这可能需要这么长时间?似乎一个简单的平方操作,确实是高度可并行化的,应该真的不需要这么长时间。它可能是由于字典的人口(如果是这样,是什么导致那里的减速?)?
技术细节:
答案 0 :(得分:2)
您是否正在使用异步线程来尝试并发CPU绑定工作?我不推荐它。而是使用进程,否则随着线程池大小的增加,GIL会越来越慢。
[编辑1]
提到David Beazly(sp?)的GIL解释的类似问题。
答案 1 :(得分:2)
我还没有试过期货,但我相信它是基于线程的,所以这可能适用: http://www.youtube.com/watch?v=ph374fJqFPE
简而言之,I / O绑定的工作负载在CPython中很好地进行,但CPU绑定的工作负载却没有。如果你在同一个进程中混合使用I / O绑定和CPU绑定的线程,那么它也不能很好地解决。
如果这是问题所在,我建议增加工作块的大小(只是将数字平方很小),然后使用multiprocessing。多处理是类似线程的,但它使用多个进程和共享内存,并且往往会使程序组件之间的耦合比线程更松散。
那,或者切换到Jython或IronPython;据说这些都很好。
答案 2 :(得分:1)
Python有global interpreter lock,它不允许同时在不同的线程中执行相同进程的Python代码。
要实现真正的并行执行,您必须使用多个进程(易于切换到ProcessPoolExecutor
)或本机(非Python,例如C)代码。
答案 3 :(得分:1)
与其他答案相反,我声称这里的主要罪魁祸首不是GIL(虽然这是一个问题),而是使用线程的开销。
在系统级线程之间产生和切换的开销很小(小于1ms),但仍可能超过平方单个整数的成本。理想情况下,当您使用任何类型的并行性时,您希望将计算分解为更大的部分(可能是一百万个整数)。
如果使用数字Python堆栈(NumPy / Pandas / C / Fortran / Cython / Numba),则可以绕过GIL。例如,以下函数将对一组数字进行平方并释放GIL。
import numpy as np
x = np.array(my_list)
import numba
@numba.jit(nogil=True)
def square(x):
for i in range(len(x)):
x[i] = x[i]**2
return x
或者大多数numpy操作都会释放GIL
x = x**2
只需平方整数,系统就无法使用多个核心。您的CPU能够以比内存层次结构更快的速度平方整数。