为什么我的Numpy测试代码比Matlab慢2倍

时间:2014-04-15 01:40:55

标签: python matlab numpy performance

我一直在用Python开发一个基于菲涅耳系数的反射率求解器,我遇到了一些障碍,因为Python + Numpy的性能比Matlab慢2倍。我已经将问题代码提炼成一个简单的示例,以显示在每种情况下执行的操作:

测试用例的Python代码:

import numpy as np
import time

def compare_fn(i):
    a = np.random.rand(400)
    vec = np.random.rand(400)
    t = time.time()
    for j in xrange(i):
        a = (2.3 + a * np.exp(2j*vec))/(1 + (2.3 * a * np.exp(2j*vec)))
    print (time.time()-t)
    return a

a = compare_fn(200000)
  

输出:10.7989997864

等效的Matlab代码:

function a = compare_fn(i)

    a = rand(1, 400);
    vec = rand(1, 400);
    tic
    for m = 1:i
        a = (2.3 + a .* exp(2j*vec))./(1 + (2.3 * a .* exp(2j*vec)));
    end
    toc
  
    
      

a = compare_fn(200000);       经过的时间是5.644673秒。

    
  

我很难过。我已经安装了MKL(Anaconda Academic License)。我非常感谢任何帮助,以确定我的例子中的问题,如果有的话,以及我如何使用Numpy获得相同的性能。

一般情况下,我不能并行化循环,因为求解多层的菲涅耳系数涉及递归计算,可以用上面的循环形式表示。

3 个答案:

答案 0 :(得分:3)

以下类似于unutbu删除的答案,并且您的示例输入在我的系统上运行速度提高了3倍。如果你在Matlab中这样实现它可能会运行得更快,但这是一个不同的故事。为了能够使用ipython的%timeit功能,我将原来的功能重写为:

def fn(a, vec, i):
    for j in xrange(i):
        a = (2.3 + a * np.exp(2j*vec))/(1 + (2.3 * a * np.exp(2j*vec)))
    return a

我已经通过从循环中删除指数计算来优化它:

def fn_bis(a, vec, n):
    exp_vec = np.exp(2j*vec)
    for j in xrange(n):
        a = (2.3 + a * exp_vec) / (1 + 2.3 * a * exp_vec)
    return a

采用两种方法进行试乘:

In [2]: a = np.random.rand(400)

In [3]: vec = np.random.rand(400)

In [9]: np.allclose(fn(a, vec, 100), fn_bis(a, vec, 100))
Out[9]: True

In [10]: %timeit fn(a, vec, 100)
100 loops, best of 3: 8.43 ms per loop

In [11]: %timeit fn_bis(a, vec, 100)
100 loops, best of 3: 2.57 ms per loop

In [12]: %timeit fn(a, vec, 200000)
1 loops, best of 3: 16.9 s per loop

In [13]: %timeit fn_bis(a, vec, 200000)
1 loops, best of 3: 5.25 s per loop

答案 1 :(得分:2)

在我最初的问题中,我一直在做很多尝试来尝试确定Matlab和Python / Numpy之间速度差异的来源。一些主要发现是:

  1. Matlab现在有一个JIT编译器,它在涉及循环的情况下提供了显着的好处。关闭它会使性能降低2倍,使其速度与原生Python + Numpy代码相似。

    功能加速

    a = compare_fn(200000);

    经过的时间是9.098062秒。

  2. 然后我开始探索使用Numba和Cython优化我的示例函数的选项,看看我能做得多好。对我来说,一个重要的发现是,在显式循环计算上的 Numba JIT优化比在Numpy数组上的本机矢量化数学运算更快。我不太明白为什么会这样,但我已经在下面列出了我的示例代码和测试时间。我也玩Cython(我不是专家)虽然它也更快,Numba仍然比Cython快2倍,所以我最终坚持使用Numba进行测试。

  3. 以下是3个等效函数的代码。第一个是Numba优化函数,具有执行元素计算的显式循环。第二个函数是依赖于Numpy向量化来执行计算的Python + Numpy函数。第三个函数尝试使用Numba来优化向量化的Numpy代码(并且无法改进,如结果中所示)。最后,我已经包含了Cython代码,但我只测试了一个案例。

    import numpy as np
    import numba as nb
    
    @nb.jit(nb.complex128[:](nb.int16, nb.int16))
    def compare_fn_jit(i, j):
        a = np.asarray(np.random.rand(j), dtype=np.complex128)
        vec = np.random.rand(j)
        exp_term = np.exp(2j*vec)
    
        for k in xrange(i):
            for l in xrange(j):
                a[l] = (2.3 + a[l] * exp_term[l])/(1 + (2.3 * a[l] * exp_term[l]))
        return a
    
    def compare_fn(i, j):
        a = np.asarray(np.random.rand(j), dtype=np.complex128)
        vec = np.random.rand(j)
        exp_term = np.exp(2j*vec)
        for k in xrange(i):
            a = (2.3 + a * exp_term)/(1 + (2.3 * a * exp_term))
        return a
    
    compare_fn_jit2 = nb.jit(nb.complex128[:](nb.int16, nb.int16))(compare_fn)
    
    
    import numpy as np
    cimport numpy as np
    cimport cython
    @cython.boundscheck(False)
    def compare_fn_cython(int i, int j):
        cdef int k, l
        cdef np.ndarray[np.complex128_t, ndim=1] a, vec, exp_term
        a = np.asarray(np.random.rand(j), dtype=np.complex128)
        vec = np.asarray(np.random.rand(j), dtype=np.complex128)
        exp_term = np.exp(2j*vec)
    
        for k in xrange(i):
            for l in xrange(j):
                a[l] = (2.3 + a[l] * exp_term[l])/(1 + (2.3 * a[l] * exp_term[l]))
        return a
    

    时间安排结果:

    我。单个外环的时序 - 证明矢量化计算的效率

      

    %timeit -n 1 -r 10 compare_fn_jit(1,1000000)1个循环,最好10个:352   每循环ms

         

    %timeit -n 1 -r 10 compare_fn(1,1000000)1个循环,最好的10:498 ms   每个循环

         

    %timeit -n 1 -r 10 compare_fn_jit2(1,1000000)1个循环,最好的10:497   每循环ms

         

    %timeit -n 1 -r 10 compare_fn_cython(1,1000000)1个循环,最好的10个:   每个循环424毫秒

    II。在大型循环的极端情况下进行计时,在短数组上进行计算(期望Numpy + Python表现不佳)

      

    %timeit -n 1 -r 5 compare_fn_jit(1000000,40)1个循环,最好的5:1.44   s per loop

         

    %timeit -n 1 -r 5 compare_fn(1000000,40)1个循环,最佳5:28.2秒   每个循环

         

    %timeit -n 1 -r 5 compare_fn_jit2(1000000,40)1个循环,最佳5:29秒   每个循环

    III。测试上面两个案例中途的某个地方

      

    %timeit -n 1 -r 5 compare_fn_jit(100000,400)1个循环,最好5:1.4 s   每个循环

         

    %timeit -n 1 -r 5 compare_fn(100000,400)1个循环,最佳5:5.26 s   每个循环

         

    %timeit -n 1 -r 5 compare_fn_jit2(100000,400)1个循环,最好的5:5.34   s per loop

    正如您所看到的,对于这种特殊情况,使用Numba可以将效率提高1.5倍至30倍。与Cython相比,我对它的效率以及使用和实现的容易程度印象深刻。

答案 2 :(得分:0)

我不知道你所做的事情是否已经足够了,但是你可以尝试一下。

http://buildbot.pypy.org/numpy-status/latest.html