concurrent.futures.ThreadPoolExecutor.map比for循环慢

时间:2014-01-18 21:42:05

标签: python multithreading python-3.x threadpoolexecutor concurrent.futures

我正在使用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个线程,因为我看到每个核心上的处理器负载。但即使使用共享内存(我可以理解为什么进程可能需要一段时间,由于内存复制),我觉得运行时的这种差异太大了。

有没有人知道为什么这可能需要这么长时间?似乎一个简单的平方操作,确实是高度可并行化的,应该真的不需要这么长时间。它可能是由于字典的人口(如果是这样,是什么导致那里的减速?)?

技术细节

  • Python 3.3.3
  • 四核(具有超高频的8个逻辑核)CPU
  • MAC OSX 10.9.1(Mavericks)

4 个答案:

答案 0 :(得分:2)

您是否正在使用异步线程来尝试并发CPU绑定工作?我不推荐它。而是使用进程,否则随着线程池大小的增加,GIL会越来越慢。

[编辑1]

提到David Beazly(sp?)的GIL解释的类似问题。

Python code performance decreases with threading

答案 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),但仍可能超过平方单个整数的成本。理想情况下,当您使用任何类型的并行性时,您希望将计算分解为更大的部分(可能是一百万个整数)。

绕过GIL

如果使用数字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能够以比内存层次结构更快的速度平方整数。