我一直在用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获得相同的性能。
一般情况下,我不能并行化循环,因为求解多层的菲涅耳系数涉及递归计算,可以用上面的循环形式表示。
答案 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之间速度差异的来源。一些主要发现是:
Matlab现在有一个JIT编译器,它在涉及循环的情况下提供了显着的好处。关闭它会使性能降低2倍,使其速度与原生Python + Numpy代码相似。
功能加速
a = compare_fn(200000);
经过的时间是9.098062秒。
然后我开始探索使用Numba和Cython优化我的示例函数的选项,看看我能做得多好。对我来说,一个重要的发现是,在显式循环计算上的 Numba JIT优化比在Numpy数组上的本机矢量化数学运算更快。我不太明白为什么会这样,但我已经在下面列出了我的示例代码和测试时间。我也玩Cython(我不是专家)虽然它也更快,Numba仍然比Cython快2倍,所以我最终坚持使用Numba进行测试。
以下是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)
我不知道你所做的事情是否已经足够了,但是你可以尝试一下。