对于我最初的问题中的示例,我一直在做很多实验来尝试确定 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 优化函数,具有显式循环来执行元素计算。第二个函数是一个 Python+Numpy 函数,它依赖于 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
每个循环的毫秒数
%timeit -n 1 -r 10 compare_fn(1,1000000) 1 次循环,最好的 10 次:498 毫秒
每个循环
%timeit -n 1 -r 10 compare_fn_jit2(1,1000000) 1 个循环,最好的 10:497
每个循环的毫秒数
%timeit -n 1 -r 10 compare_fn_cython(1,1000000) 1 次循环,最好的 10:
每个循环 424 毫秒
二。对短数组进行计算的大循环极端情况下的计时(预计 Numpy+Python 性能不佳)
%timeit -n 1 -r 5 compare_fn_jit(1000000,40) 1 个循环,最好的 5 个:1.44
每个循环的秒数
%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 s
每个循环
三。测试上述两种情况的中间位置
%timeit -n 1 -r 5 compare_fn_jit(100000,400) 1 次循环,最好的 5 次:1.4 秒
每个循环
%timeit -n 1 -r 5 compare_fn(100000,400) 1 次循环,最好的 5 次:5.26 秒
每个循环
%timeit -n 1 -r 5 compare_fn_jit2(100000,400) 1 个循环,最好的 5 个:5.34
每个循环的秒数
如您所见,对于这种特殊情况,使用 Numba 可以将效率提高 1.5 倍到 30 倍不等。与 Cython 相比,它的效率以及使用和实施的容易程度给我留下了深刻的印象。