2013-10-09 39 views
13

我一直在玩弄numba和numexpr,试图加速一个简单的基于元素的矩阵乘法。我一直无法获得更好的结果,它们基本上都是(快速)等同于numpys乘法函数。有没有人在这方面有幸运?我使用numba和numexpr是否有错误(我对此很陌生),或者这是一种糟糕的方法来尝试加快速度。这里是一个可重复的代码,谢谢你的高级:在python中加速元素数组乘法

import numpy as np 
from numba import autojit 
import numexpr as ne 

a=np.random.rand(10,5000000) 

# numpy 
multiplication1 = np.multiply(a,a) 

# numba 
def multiplix(X,Y): 
    M = X.shape[0] 
    N = X.shape[1] 
    D = np.empty((M, N), dtype=np.float) 
    for i in range(M): 
     for j in range(N): 
      D[i,j] = X[i, j] * Y[i, j] 
    return D 

mul = autojit(multiplix) 
multiplication2 = mul(a,a) 

# numexpr 
def numexprmult(X,Y): 
    M = X.shape[0] 
    N = X.shape[1] 
    return ne.evaluate("X * Y") 

multiplication3 = numexprmult(a,a) 
+0

'numexpr'可以一枝独秀'像这样ufunc般的操作numpy',尤其是几个串在一起。另外,如果您有多个内核,请尝试设置'ne.set_num_cores(N)',其中'N'是您的计算机的核心数。 – askewchan

+1

在我的机器上,基于'numexpr'的函数比在单个内核上运行的'np.multiply()'运行速度慢大约15%,但是当我将内核数量设置为8时,它的速度会降低大约2倍。记住,你可能会发现你必须重置你的Python进程的核心关系才能使用多个核心 - [请参阅我的答案](http://stackoverflow.com/a/15641148/1461210)。 –

+0

您可以尝试使用[Theano]使用您的GPU(https://github.com/Theano/Theano)。我真的不知道它是否会有所帮助,结果将取决于您的确切硬件,但它可能值得一试。 [这里](https://groups.google.com/forum/#!topic/theano-users/fZpCchn4JbI)你会找到一个如何使用Theano进行元素矩阵乘法的例子。 –

回答

11

如何使用

elementwise.F90:

subroutine elementwise(a, b, c, M, N) bind(c, name='elementwise') 
    use iso_c_binding, only: c_float, c_int 

    integer(c_int),intent(in) :: M, N 
    real(c_float), intent(in) :: a(M, N), b(M, N) 
    real(c_float), intent(out):: c(M, N) 

    integer :: i,j 

    forall (i=1:M,j=1:N) 
    c(i,j) = a(i,j) * b(i,j) 
    end forall 

end subroutine 

elementwise.py:

from ctypes import CDLL, POINTER, c_int, c_float 
import numpy as np 
import time 

fortran = CDLL('./elementwise.so') 
fortran.elementwise.argtypes = [ POINTER(c_float), 
           POINTER(c_float), 
           POINTER(c_float), 
           POINTER(c_int), 
           POINTER(c_int) ] 

# Setup  
M=10 
N=5000000 

a = np.empty((M,N), dtype=c_float) 
b = np.empty((M,N), dtype=c_float) 
c = np.empty((M,N), dtype=c_float) 

a[:] = np.random.rand(M,N) 
b[:] = np.random.rand(M,N) 


# Fortran call 
start = time.time() 
fortran.elementwise(a.ctypes.data_as(POINTER(c_float)), 
        b.ctypes.data_as(POINTER(c_float)), 
        c.ctypes.data_as(POINTER(c_float)), 
        c_int(M), c_int(N)) 
stop = time.time() 
print 'Fortran took ',stop - start,'seconds' 

# Numpy 
start = time.time() 
c = np.multiply(a,b) 
stop = time.time() 
print 'Numpy took ',stop - start,'seconds' 

予编译使用

gfortran -O3 -funroll-loops -ffast-math -floop-strip-mine -shared -fPIC \ 
     -o elementwise.so elementwise.F90 

输出的文件的Fortran产生的加速〜10 %:

$ python elementwise.py 
Fortran took 0.213667869568 seconds 
Numpy took 0.230120897293 seconds 
$ python elementwise.py 
Fortran took 0.209784984589 seconds 
Numpy took 0.231616973877 seconds 
$ python elementwise.py 
Fortran took 0.214708089828 seconds 
Numpy took 0.25369310379 seconds 
+0

可爱的答案。加速并不是真的令人印象深刻,但我有兴趣在玩这个,谢谢 – JEquihua

+2

可爱的答案就像JEquihua说的那样。答案是,必须先做一个fortran调用才能初始化共享库,第二个调用是最能提供敏感答案的调用,加速应该在50%左右,另一种方法是使用循环假设有100个相同函数的调用)并且取平均时间 – innoSPG

+0

加速为什么会在50%左右?怎么样?@innoSPG – JEquihua

4

编辑:从来没有这个答案,我错了(见下面的评论)。


恐怕在python中比使用numpy更快的矩阵乘法是非常非常困难的。 NumPy通常使用像ATLAS/LAPACK这样的内部fortran库,这些库非常好的优化。

要检查您的NumPy的版本与LAPACK支持内置:打开一个终端,进入你的Python安装目录,然后键入:

for f in `find lib/python2.7/site-packages/numpy/* -name \*.so`; do echo $f; ldd $f;echo "\n";done | grep lapack 

注意,路径可以根据你的Python版本而异。 如果你打印了一些行,你肯定会支持LAPACK ......所以在单个内核上实现更快的矩阵乘法将很难实现。

现在我不知道使用多个内核来执行矩阵乘法,所以你可能想看看(请参阅ali_m的评论)。

+2

外部BLAS/LAPACK库仅与线性代数运算(如_matrix_乘法)相关。在OP的例子中,_Elementwise_乘法使用一个用C代码编写的['ufunc'](http://docs.scipy.org/doc/numpy/reference/ufuncs.html),它是numpy的一个内在组件。话虽如此,但我的感觉是,对于这些方法中的任何一种来说,都会要求很高的代码量来处理手写C代码的速度,以便像元素乘法那样简单。 –

6

你最近在做什么?

随机数组的创建占用了整个计算的一部分,如果将​​其包含在您的计算时间内,您几乎不会在结果中看到任何实际差异,但是,如果您在前面创建它,实际上比较方法。

这是我的结果,我一直在看你在看什么。 numpy的和numba给出大致相同的结果(numba是快一点点。)

(我没有可用numexpr)

In [1]: import numpy as np 
In [2]: from numba import autojit 
In [3]: a=np.random.rand(10,5000000) 

In [4]: %timeit multiplication1 = np.multiply(a,a) 
10 loops, best of 3: 90 ms per loop 

In [5]: # numba 

In [6]: def multiplix(X,Y): 
    ...:   M = X.shape[0] 
    ...:   N = X.shape[1] 
    ...:   D = np.empty((M, N), dtype=np.float) 
    ...:   for i in range(M): 
    ...:     for j in range(N): 
    ...:       D[i,j] = X[i, j] * Y[i, j] 
    ...:   return D 
    ...:   

In [7]: mul = autojit(multiplix) 

In [26]: %timeit multiplication1 = np.multiply(a,a) 
10 loops, best of 3: 182 ms per loop 

In [27]: %timeit multiplication1 = np.multiply(a,a) 
10 loops, best of 3: 185 ms per loop 

In [28]: %timeit multiplication1 = np.multiply(a,a) 
10 loops, best of 3: 181 ms per loop 

In [29]: %timeit multiplication2 = mul(a,a) 
10 loops, best of 3: 179 ms per loop 

In [30]: %timeit multiplication2 = mul(a,a) 
10 loops, best of 3: 180 ms per loop 

In [31]: %timeit multiplication2 = mul(a,a) 
10 loops, best of 3: 178 ms per loop 

更新: 我使用了最新版本的numba的,只是compiled it from source: '0.11.0-3-gea20d11脏'

我Fedora中19使用默认numpy的测试此, '1.7.1' numpy的 '1.6.1' 从源代码编译,对链接:

Update3 我以前的结果当然是不正确的,我在内循环中返回了D,所以跳过了90%的计算。

这为ali_m的假设提供了更多的证据,证明它比已经非常优化的c代码真的很难做得更好。

但是,如果您尝试do something more complicated,例如,,

np.sqrt(((X[:, None, :] - X) ** 2).sum(-1)) 

我可以重现的数字杰克Vanderplas得到的:

In [14]: %timeit pairwise_numba(X) 
10000 loops, best of 3: 92.6 us per loop 

In [15]: %timeit pairwise_numpy(X) 
1000 loops, best of 3: 662 us per loop 

因此,看来你正在做的事情已经由numpy的迄今最优化很难做得更好。

+0

我正在使用'%% a = np.random.rand(10,5000000)\ mul(a,a)'来计时 - 数组的创建并未包含在定时计算中。你使用哪个版本的numba和numpy? –

+0

@ali_m我在我的帖子中回答。 –

+0

有趣......我开始怀疑可能会有一些微妙的破坏我的当前numba/pyllvm/llvm设置(对于numba版本比v0.10.2更新版本,我遇到了一个编译器错误)。我会深入研究它 - 也许它可能与OP正在经历的事情有关。 –