【问题标题】:why isn't numpy.mean multithreaded?为什么不是 numpy.mean 多线程?
【发布时间】:2013-05-13 03:34:30
【问题描述】:

我一直在寻找方法来轻松地对一些简单的分析代码进行多线程处理,因为我注意到 numpy 它只使用一个内核,尽管它应该是多线程的。

我知道 numpy 是为多个内核配置的,因为我可以看到使用 numpy.dot 的测试使用了我所有的内核,所以我只是将 mean 重新实现为一个点积,它运行得更快。是否有某种原因意味着无法自行运行这么快?我发现较大数组的行为类似,尽管该比率比我的示例中显示的 3 接近 2。

我一直在阅读大量关于类似 numpy 速度问题的帖子,显然它的方式比我想象的要复杂。任何见解都会有所帮助,我宁愿只使用均值,因为它更具可读性且代码更少,但我可能会切换到基于点的均值。

In [27]: data = numpy.random.rand(10,10)

In [28]: a = numpy.ones(10)

In [29]: %timeit numpy.dot(data,a)/10.0
100000 loops, best of 3: 4.8 us per loop

In [30]: %timeit numpy.mean(data,axis=1)
100000 loops, best of 3: 14.8 us per loop

In [31]: numpy.dot(data,a)/10.0 - numpy.mean(data,axis=1)
Out[31]: 
array([  0.00000000e+00,   0.00000000e+00,   0.00000000e+00,
         0.00000000e+00,   1.11022302e-16,   0.00000000e+00,
         0.00000000e+00,   0.00000000e+00,   0.00000000e+00,
        -1.11022302e-16])

【问题讨论】:

  • @Riateche numpy的核心是用C写的。
  • 一定要使用类似mean 的函数,而不是到处做点的事情,所以如果numpy.mean 改进了,你可以在以后更改它。
  • @Riateche:numpy 也会在有用的时候发布 GIL。
  • Blaze 在(不是那么遥远?)未来。
  • 另见numexpr。它可能无法执行您想要的操作,但如果可以,它通常会自动执行多线程处理,并将其 JIT 转换为比普通 numpy 更快的启动方式。 (因为它是基于 JIT 的,您通常看不到小玩具示例与实际示例具有相同的好处,因此您可能需要在大量真实代码上对其进行测试,以确定它是否值得。)跨度>

标签: python multithreading performance numpy


【解决方案1】:

自从我注意到 numpy 它只使用一个内核,尽管它应该是多线程的,我一直在寻找方法来轻松地对一些简单的分析代码进行多线程处理。

谁说它应该是多线程的?

numpy 的主要设计目的是在单核上尽可能快,并在需要时尽可能并行化。但是你仍然需要并行化它。

特别是,您可以同时对独立的子对象进行操作,并且缓慢的操作会在可能的情况下释放 GIL——尽管“尽可能”可能还不够。此外,numpy 对象旨在尽可能轻松地在进程之间共享或传递,以方便使用multiprocessing

有一些专门的方法可以自动并行化,但大多数核心方法不是。特别是,dot 尽可能在 BLAS 之上实现,并且 BLAS 在大多数平台上自动并行化,但mean 是用纯 C 代码实现的。

详情请见Parallel Programming with numpy and scipy


那么,您如何知道哪些方法是并行化的,哪些不是?而且,在那些不是,你怎么知道哪些可以很好地手动线程化,哪些需要多处理?

对此没有好的答案。您可以做出有根据的猜测(X 似乎可能是在 ATLAS 之上实现的,而我的 ATLAS 副本是隐式线程的),或者您可以阅读源代码。

但通常情况下,最好的办法是尝试并测试。如果代码使用 100% 的一个内核和 0% 的其他内核,请添加手动线程。如果它现在使用 100% 的一个内核和 10% 的其他内核并且几乎没有运行得更快,请将多线程更改为多处理。 (幸运的是,Python 让这很容易,特别是如果您使用来自 concurrent.futures 的 Executor 类或来自 multiprocessing 的 Pool 类。但您仍然经常需要考虑一下,并测试共享与共享的相对成本。如果您有大型数组,则通过。)

另外,正如 kwatford 指出的那样,仅仅因为某些方法似乎不是隐式并行的,并不意味着它不会在下一版本的 numpy、下一版本的 BLAS 或不同的平台,甚至在安装了稍微不同的东西的机器上。所以,准备重新测试。并执行my_mean = numpy.mean 之类的操作,然后在任何地方使用my_mean,这样您只需将一行更改为my_mean = pool_threaded_mean

【讨论】:

  • 可能需要再次更新。
【解决方案2】:

基本上,因为 BLAS 库有一个优化的点积,他们可以轻松调用 dot,它本质上是并行的。他们承认他们可以扩展 numpy 以并行化其他操作,但选择不走那条路。然而,他们给出了一些关于如何并行化你的 numpy 代码的技巧(基本上是在 N 个核心之间划分工作(例如,N=4),将你的数组分成 N 个子数组,并将每个子数组的作业发送到它自己的线程和然后结合你的结果)。

http://wiki.scipy.org/ParallelProgramming

使用并行原语

numpy 的一大优点是可以非常简洁地表达数组操作。例如,要计算矩阵 A 和矩阵 B 的乘积,您只需:

>>> C = numpy.dot(A,B)

这不仅读起来简单明了,而且由于 numpy 知道您想要做一个矩阵点积,它可以使用作为“BLAS”(基本线性代数子例程)的一部分获得的优化实现。这通常是一个经过仔细调整的库,通过利用高速缓存和汇编器实现在您的硬件上尽可能快地运行。但是现在许多架构都有一个 BLAS,它也利用了多核机器。如果您的 numpy/scipy 是使用其中之一编译的,那么 dot() 将在您不执行任何操作的情况下并行计算(如果这更快)。类似的其他矩阵运算,如求逆、奇异值分解、行列式等。例如,开源库 ATLAS 允许在编译时选择并行度(线程数)。来自英特尔的专有 MKL 库提供了在运行时选择并行级别的可能性。还有允许运行时选择并行级别的 GOTO 库。这是一个商业产品,但源代码免费分发供学术使用。

最后,scipy/numpy 不会像

那样并行化操作

>>> A = B + C

>>> A = numpy.sin(B)

>>> A = scipy.stats.norm.isf(B)

这些操作按顺序运行,不利用多核机器(见下文)。原则上,这可以在不做太多工作的情况下改变。 OpenMP 是 C 语言的扩展,它允许编译器为适当注释的循环(和其他事物)生成并行化代码。如果有人坐下来在 numpy(也可能在 scipy)中注释了几个核心循环,然后如果有人在打开 OpenMP 的情况下编译 numpy/scipy,那么上述所有三个都将自动并行运行。当然,实际上人们会想要一些运行时控制 - 例如,如果一个人计划在同一台多处理器机器上运行多个作业,则可能想要关闭自动并行化。

【讨论】:

  • +1。唯一的问题是他们不保证在任何地方哪些操作释放 GIL 和/或在部分子对象上独立操作,只是他们试图使所有内容都可线程化。因此,您几乎必须测试手动线程,交叉手指,并准备好在必要时退回到多处理(并且,对于足够大的阵列,您还必须测试共享与 IPC 传递)。幸运的是,Python 可以很容易地在必要时回退到多处理,所以这并不是什么大不了的事情。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-07
  • 1970-01-01
  • 2011-04-16
  • 2014-07-21
  • 2011-06-17
相关资源
最近更新 更多