如何在python中提高for循环的性能

时间:2019-01-16 20:36:43

标签: python matlab numpy

我正在用python做很多模拟,模拟系统响应。

我目前一直在使用Runge-Kutta方案,但偶然发现了我正在测试的另一种方案。

在Matlab中进行测试时,与我的Runge-Kutta相比,我获得了出色的性能。但是,当我将其转移到Python时,它的速度明显慢了。

我不确定这是不是真的,或者我是否可以改善编码方式,所以如果可能的话,我想听听您的一些意见。

Matlab中的代码示例:

dt = 0.0001;
f = randn(1, (60 / dt));
ns = length(f);
yo = zeros(3,1);
P1 = [0; 0.001; 0];
F = [1 0.0001 0; 0.001 1 0.0001; 0.001 0 1];
y1 = zeros(3, ns);
tic
for i = 1:ns
    y1(:, i) = P1*f(:, i) + F*yo;
    yo = y1(:, i);
end
toc

其中循环在0.55-0.61秒内执行。

Python中的代码,例如:

dt = 0.0001
f = np.random.randn(1, int(60 / dt))
ns = np.size(f)
yo = np.zeros((3))
F = np.array([[1, 0.0001, 0], [0.001, 1, 0.0001], [0.001, 0, 1]])
P1 = np.transpose(np.array([[0, 0.0001, 0]]))
y1 = np.zeros((3, ns), order='F')
start_time = time.time()
for i in range(ns-1):
    y1[:, i] = np.dot(P1, f[:, i]) + np.reshape(np.dot(F, yo), (3))
    yo = y1[: , i]
print("--- %s seconds ---" % (time.time() - start_time))

其中循环在2.8 -3.1秒内执行。

我可以做些改进吗?

感谢您考虑我的问题。

2 个答案:

答案 0 :(得分:2)

我建议在评论中使用numba。这是一个示例:

import numba
import numpy as np

def py_func(dt, F, P1):
    f = np.random.randn(1, int(60 / dt))
    ns = f.size
    yo = np.zeros((3))
    y1 = np.zeros((3, ns), order='F')
    for i in range(ns-1):
        y1[:, i] = np.dot(P1, f[:, i]) + np.reshape(np.dot(F, yo), (3))
        yo = y1[: , i]
    return yo

@numba.jit(nopython=True)
def numba_func(dt, F, P1):
    f = np.random.randn(1, int(60 / dt))
    ns = f.size
    yo = np.zeros((3))
    y1 = np.zeros((3, ns))
    for i in range(ns-1):
        y1[:, i] = np.dot(P1, f[:, i]) + np.reshape(np.dot(F, yo), (3))
        yo = y1[: , i]
    return yo

numba不能使用'F'顺序,因为它使用C型数组,而不是FORTRAN数组。

时间差异如下所示:

纯python循环:

%%timeit
py_func(dt, F, P1)

结果:

2.88 s ± 100 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Numba:

%%timeit
numba_func(dt, F, P1)

结果:

588 ms ± 10.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

答案 1 :(得分:1)

我对您的代码进行了一些优化,对我来说,执行时间从2.8s减少到1.2s左右。在寻找更快的解释器之前,我建议您进行性能分析(请参阅line_profiler),并尝试从最内层循环中删除所有可能的内容。更好的方法是,尝试避免任何显式的“ for”循环,并依赖于numpy函数,例如点,einsum等。

我想仍然有一些优化的地方。我认为我没有改变您的价值观,但请您检查一下。借助numba或cython(cython.org)或pypy(pypy.org)等其他工具,我认为您的执行时间将大大提高。

#!/usr/bin/env python3

import numpy as np
import time

np.random.seed(0)

#@profile
def run():
    dt = 0.0001
    f = np.random.randn(1, int(60 / dt))
    ns = np.size(f)
    yo = np.zeros((3))
    F = np.array([[1, 0.0001, 0], [0.001, 1, 0.0001], [0.001, 0, 1]])
    P1 = np.transpose(np.array([[0, 0.0001, 0]]))
    start_time = time.time()
    y1 = np.outer(f, P1)
    for i in range(ns-1):
        y1[i] += F@yo
        yo = y1[i]
    print("--- %s seconds ---" % (time.time() - start_time))
    y1 = y1.T
    print(yo)

run()