我的python代码运行大约6.2秒。 Matlab代码在0.05秒内运行。为什么会这样,我该怎么做才能加快Python代码的速度? Cython解决方案吗?
Matlab:
function X=Test
nIter=1000000;
Step=.001;
X0=1;
X=zeros(1,nIter+1); X(1)=X0;
tic
for i=1:nIter
X(i+1)=X(i)+Step*(X(i)^2*cos(i*Step+X(i)));
end
toc
figure(1) plot(0:nIter,X)
Python:
nIter = 1000000
Step = .001
x = np.zeros(1+nIter)
x[0] = 1
start = time.time()
for i in range(1,1+nIter):
x[i] = x[i-1] + Step*x[i-1]**2*np.cos(Step*(i-1)+x[i-1])
end = time.time()
print(end - start)
答案 0 :(得分:1)
您最大的耗时是np.cos
,它对输入格式进行多次检查。
这些是相关的,对于高维输入通常可以忽略不计,但是对于一维输入,这成为了瓶颈。
解决方案是使用math.cos
,它仅接受一维数字作为输入,因此速度更快(尽管灵活性较差)。
另一个耗时的指标多次索引x
。
您可以通过更新一个状态变量并每次迭代仅写入一次x
来加快速度。
通过所有这些,您可以将速度提高大约十倍:
import numpy as np
from math import cos
nIter = 1000000
Step = .001
x = np.zeros(1+nIter)
state = x[0] = 1
for i in range(nIter):
state += Step*state**2*cos(Step*i+state)
x[i+1] = state
现在,您的主要问题是,您真正的最内层循环完全发生在Python中,即您有很多包装操作会占用时间。
您可以通过使用uFunc(例如,由SymPy的ufuncify
创建)和NumPy的accumulate
来避免这种情况:
import numpy as np
from sympy.utilities.autowrap import ufuncify
from sympy.abc import t,y
from sympy import cos
nIter = 1000000
Step = 0.001
state = x[0] = 1
f = ufuncify([y,t],y+Step*y**2*cos(t+y))
times = np.arange(0,nIter*Step,Step)
times[0] = 1
x = f.accumulate(times)
这实际上是在瞬间完成的。
如果您所关心的只是您的确切代码,那么您就不必担心运行时,因为这两种方法都很短。 另一方面,如果您使用它来评估运行时间较长的问题的效率,则示例将失败,因为它仅考虑一个初始条件并且是非常简单的动态。
此外,您正在使用Euler方法,根据步长的不同,该方法不是非常有效或不够可靠。
在您的情况下,后者(Step
)太低了,产生的数据比您可能需要的多得多:
步长为1时,您可以看到正在发生的事情。
如果您想在这种情况下进行可靠的集成,最好总是使用现代的自适应集成器,它可以自行调整步长,例如,这是使用本机Python集成器解决问题的一种方法:
from math import cos
import numpy as np
from scipy.integrate import solve_ivp
T = 1000
dt = 0.001
x = solve_ivp(
lambda t,state: state**2*cos(t+state),
t_span = (0,T),
t_eval = np.arange(0,T,dt),
y0 = [1],
rtol = 1e-5
).y
这将根据误差容限rtol
自动将步长调整为更高的值。
它仍然返回相同数量的输出数据,但这是通过解决方案的插值来实现的。
对我来说它的运行时间为0.3秒。
如果您仍然需要加快这样的速度,则可能是导数(f
)比示例中复杂得多,因此是瓶颈。
根据您的问题,您可以将其计算结果矢量化(使用NumPy或类似方法)。
如果您无法向量化,我写了一个module,专门针对此问题,通过对您的派生词进行了硬编码。 这是您的示例,采样步骤为1。
import numpy as np
from jitcode import jitcode,y,t
from symengine import cos
T = 1000
dt = 1
ODE = jitcode([y(0)**2*cos(t+y(0))])
ODE.set_initial_value([1])
ODE.set_integrator("dop853")
x = np.hstack([ODE.integrate(t) for t in np.arange(0,T,dt)])
这会在瞬间重新运行。尽管这可能与提高速度无关,但这可以扩展到大型系统。
答案 1 :(得分:0)
差异是jit编译,Matlab默认使用该编译。让我们使用Numba
(Python jit编译器)
代码
import numba as nb
import numpy as np
import time
nIter = 1000000
Step = .001
@nb.njit()
def integrate(nIter,Step):
x = np.zeros(1+nIter)
x[0] = 1
for i in range(1,1+nIter):
x[i] = x[i-1] + Step*x[i-1]**2*np.cos(Step*(i-1)+x[i-1])
return x
#Avoid measuring the compilation time,
#this would be also recommendable for Matlab to have a fair comparison
res=integrate(nIter,Step)
start = time.time()
for i in range(100):
res=integrate(nIter,Step)
end=time.time()
print((end - start)/100)
这导致每次调用的运行时间为0.022s。