并行化NumPy向量操作

64

举个例子,让我们使用numpy.sin()

下面的代码将返回数组a中每个值的正弦值:

import numpy
a = numpy.arange( 1000000 )
result = numpy.sin( a )

但我的机器有32个核心,所以我想利用它们。(对于像numpy.sin()这样的东西,开销可能不值得,但我实际想要使用的函数相当复杂,并且我将处理大量数据。)

这是最好的(即最聪明或最快)方法吗:

from multiprocessing import Pool
if __name__ == '__main__':
    pool = Pool()
    result = pool.map( numpy.sin, a )

还有更好的方法吗?


如果你要使用pool.map(),你应该使用math.sin,因为它比numpy.sin更快。参考:https://dev59.com/lXA65IYBdhLWcg3wvxeE。 - Eric O. Lebigot
4
根据官方的numpy/scipy wiki页面,使用numpy.sin函数可以并行计算,但需要在编译numpy时开启openmp选项。参考链接为:https://software.intel.com/en-us/articles/numpyscipy-with-intel-mkl。 - Ziyuan
你也可以使用Bohrium:只需将你的第一行替换为import bohrium as numpy即可。 - j08lue
3个回答

75

有更好的方法:numexpr

稍微改写自它们的主页:

它是用C语言编写的多线程VM,分析表达式,更高效地重写它们,并即时将它们编译成代码,以获得几乎最佳的并行性能,适用于内存和CPU绑定的操作。

例如,在我的4核机器上,计算正弦函数比numpy快了近4倍。

In [1]: import numpy as np
In [2]: import numexpr as ne
In [3]: a = np.arange(1000000)
In [4]: timeit ne.evaluate('sin(a)')
100 loops, best of 3: 15.6 ms per loop    
In [5]: timeit np.sin(a)
10 loops, best of 3: 54 ms per loop

文档包括支持的函数在此处。您需要检查或提供更多信息,以查看您的更复杂的函数是否可以由numexpr评估。


8
我使用了numexpr编写了代码,与使用numpy相同的代码相比,速度快了约6倍。非常感谢你的建议!现在我想知道为什么numexpr不更广泛地被使用。在我寻找Python数值包的过程中,直到现在我才发现它。另外,numexpr不支持数组索引,这可能有点烦人,但几乎没有影响。 - user1475412
1
也许你还应该检查一下Theano和Cython。Theano可以使用GPU,但我并没有真正使用过,所以无法提供示例。 - jorgeca
4
Numexpr之所以没有更广泛的应用,我猜是因为它比纯NumPy更麻烦(就像上面的例子)。但它确实非常适合加速需要更快运行的NumPy计算。 - Eric O. Lebigot
3
我用这个获得了30倍的速度提升!太棒了 :) - Phani

28

如果你运行以下命令,那么这就是一个有趣的注释:

import numpy
from multiprocessing import Pool
a = numpy.arange(1000000)    
pool = Pool(processes = 5)
result = pool.map(numpy.sin, a)

UnpicklingError: NEWOBJ class argument has NULL tp_new

没想到会这样,那么发生了什么事,好吧:

>>> help(numpy.sin)
   Help on ufunc object:

sin = class ufunc(__builtin__.object)
 |  Functions that operate element by element on whole arrays.
 |  
 |  To see the documentation for a specific ufunc, use np.info().  For
 |  example, np.info(np.sin).  Because ufuncs are written in C
 |  (for speed) and linked into Python with NumPy's ufunc facility,
 |  Python's help() function finds this page whenever help() is called
 |  on a ufunc.

是的,numpy.sin是用C实现的,因此你无法直接在多进程中使用它。因此,我们需要使用另一个函数对其进行包装。

性能:

import time
import numpy
from multiprocessing import Pool

def numpy_sin(value):
    return numpy.sin(value)

a = numpy.arange(1000000)
pool = Pool(processes = 5)

start = time.time()
result = numpy.sin(a)
end = time.time()
print 'Singled threaded %f' % (end - start)
start = time.time()
result = pool.map(numpy_sin, a)
pool.close()
pool.join()
end = time.time()
print 'Multithreaded %f' % (end - start)


$ python perf.py 
Singled threaded 0.032201
Multithreaded 10.550432

哇,我也没想到那样,首先有几个问题,即使我们只是在使用包装器而不是纯C函数,我们仍然在使用Python函数,还有复制值的开销,多进程默认不共享数据,因此每个值都需要来回复制。

请注意,如果正确地分段我们的数据:

import time
import numpy
from multiprocessing import Pool

def numpy_sin(value):
    return numpy.sin(value)

a = [numpy.arange(100000) for _ in xrange(10)]
pool = Pool(processes = 5)

start = time.time()
result = numpy.sin(a)
end = time.time()
print 'Singled threaded %f' % (end - start)
start = time.time()
result = pool.map(numpy_sin, a)
pool.close()
pool.join()
end = time.time()
print 'Multithreaded %f' % (end - start)

$ python perf.py 
Singled threaded 0.150192
Multithreaded 0.055083

那么我们可以得出什么结论呢?多进程是很棒的,但我们应该经常进行测试和比较,有时它更快,有时它更慢,这取决于它的使用方式...

假设您没有使用numpy.sin而是使用另一个函数,我建议您先验证一下是否确实使用多进程会加速计算,也许来回复制值的开销会影响您。

无论如何,我也相信使用pool.map是最好、最安全的多线程代码方法...

希望这可以帮到您。


非常感谢!这非常有用。根据我所读的内容,我认为Poolmap()函数会在数据上智能地工作,但我猜先对其进行分段会有很大的区别。还有其他方法可以避免进程复制数据的开销吗?如果我使用math.sin(),您是否预计会有性能差异? - user1475412
我实际尝试了 math.sin,但它比单线程的 numpy.sin 慢得多,即使是多线程。虽然它比多线程的 numpy.sin 快(用了 6.435199 秒,而多线程的 numpy.sin 用了 10.5 秒),这可能是因为 numpy.sin 可以处理数组,numpy 的开发人员在数学方面非常擅长 ;),是的,有一种方法可以使用 shared memory http://docs.python.org/library/multiprocessing.html,但请不要使用,因为它非常危险并且支持有限,或者至少要小心处理。 - Samy Vilar
如果你只是在进行读取操作,那么可能是安全的,子进程只需要跟踪它们对应的索引或索引子集即可... - Samy Vilar

12

SciPy实际上对这个主题有一个非常好的阐述,可以在这里找到。


1
链接已经失效了,你指的是这个吗?https://scipy-cookbook.readthedocs.io/items/ParallelProgramming.html - mateuszb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接