8

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

有没有人知道为什么这可能需要这么长时间?看起来一个简单的平方运算,它确实是高度可并行化的,真的不应该花这么长时间。这可能是由于字典的数量(如果是,是什么导致那里的放缓?)?

技术细节

  • 蟒蛇 3.3.3
  • 四核(8 个带超标题的逻辑核心)CPU
  • MAC OSX 10.9.1(小牛队)
4

4 回答 4

2

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

[编辑 1]

与 David Beazly (sp?) 的 GIL 解释有关的类似问题。

Python 代码性能随线程而降低

于 2014-01-18T21:47:26.043 回答
2

我还没有尝试过期货,但我相信它是基于线程的,所以这可能适用: http ://www.youtube.com/watch?v=ph374fJqFPE

简而言之,受 I/O 限制的工作负载在 CPython 中线程很好,但受 CPU 限制的工作负载却不行。而且,如果您在同一进程中混合使用 I/O 绑定和 CPU 绑定线程,那也不会很好地处理线程。

如果这是问题所在,我建议增加你的工作块的大小(只是平方一个数字非常小),并使用multiprocessing。多处理是类似线程的,但它使用共享内存的多个进程,并且倾向于在程序组件之间提供比线程更松散的耦合。

那,或者切换到 Jython 或 IronPython;据说这些线程很好。

于 2014-01-18T21:50:01.630 回答
1

Python 具有全局解释器锁,它不允许在不同线程中同时执行同一进程的 Python 代码。要实现真正的并行执行,您必须使用多个进程(易于切换到ProcessPoolExecutor)或本机(非 Python,例如 C)代码。

于 2014-01-18T21:48:53.250 回答
1

线程有开销

与其他答案相反,我认为这里的罪魁祸首不是 GIL(尽管这是一个问题),而是使用线程的开销。

在系统级线程之间产生和切换的开销很小(小于 1 毫秒),但仍然可能超过对单个整数求平方的成本。理想情况下,当使用任何类型的并行性时,您希望将计算分成更大的部分(可能是一百万个整数的平方)。

绕过 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 对整数求平方的速度远远快于您的内存层次结构所能提供的速度。

于 2015-11-09T16:04:08.143 回答