【问题标题】:Reducing execution time in python for nested loops减少python中嵌套循环的执行时间
【发布时间】:2018-11-02 19:50:50
【问题描述】:

我为数值模拟编写了以下简单代码。我的编程水平是初学者。

import numpy as np
import time as t
start = t.time()
r=10**-8
alpha=60*(np.pi/180)
gamma_sa=58.6*10**-3 
gamma_sw=25*10**-3
gamma_pa=153*10**-3
gamma_pw=110*10**-3
gamma_aw=72.5*10**-3    
kt= 1.38*10**-23*293 
i=0
##############variables########################
omega=0 
zeta= (3/2 )*np.pi*r**3 *10**-3
dt=0.01 
std=np.sqrt(2*kt*zeta*dt)
for k in range(1,2):
    beta_i=[]
    j_i=[]
    B=[]
    time=np.arange(dt,10,dt)
    Fs_i=[]
    dE_i=[]
    j=0
    for i in range (len(time)):
        j_i.append(j)
        beta=(90-j)
        beta1=(90-j)*(np.pi/180)
        Fs=0
        Ft = (np.random.randn()*std*np.sqrt(dt))/zeta
        beta_i.append(beta)
        del(beta)
        j=(j+Ft+Fs)%360
    MSD=[]
    diff_i=[]
    tau_i=[]
    for l in range(1,len(time)):
        tau=l*dt
        tau_i.append(tau)
        del(tau)
        for i in range(1,(len(time)-l)):
            diff=(j_i[l+i]-j_i[i])**2*dt
            diff_i.append(diff)
        MSD_j=np.sum(diff_i)/np.max(time)
        MSD.append(MSD_j)
        del(MSD_j) 
    np.savetxt("MSD_no_fs%d"%k, MSD)
    np.savetxt("Tau_no_fs%d"%k,tau_i)
print(t.time() - start)

代码运行成功,执行时间约为 38 秒。但是,如果我将 dt 从.01 增加到 .001,它似乎需要无限的时间,因为脚本会继续运行而不会出现错误。有人可以解释关于 dt、k 范围和时间范围的执行时间依赖性以及任何有效的方法吗?因为我想选择 dt=.0001,krange (0,100,dt) 和时间 (dt,100,dt)。这方面的最佳做法是什么?

PS:8 GB 内存和 3.31 GHz v6 处理器。

【问题讨论】:

  • 执行时间似乎与 time 数组的长度成二次方关系,因为有两个 for 循环。因此,如果您将长度乘以 10(当您将步长减小 10 倍时,您实际上是在做什么),那么执行时间将增加 100 倍,即 3800 秒。

标签: python numpy nested-loops


【解决方案1】:

time 的长度与dt 的大小成反比。在每种情况下,您都有一个基于 time 长度的嵌套循环。只要该循环是您最热门的代码,您的代码就会经历二次增长 (O(n**2))。通过0.010.01 步进到10 意味着(足够接近)100 个元素的长度,以及~10,000 个内部循环工作单位。从0.00110 通过0.001 执行相同的操作意味着~1000 个元素和~1,000,000 个工作单位,增加了100 倍。

从 38 秒的起点开始,这是相当极端的;即使内存不是问题,您也会看到 3800 秒(超过一个小时)。记忆个问题;你的内部循环重复appends 到diff_i,所以你要从存储 ~10,000 floats (在 CPython x64 构建似乎占用 24 个字节,加上 8 个字节用于 @987654334 中的引用@),这意味着你吃掉了大约三分之一 MB 的 RAM。使用dt0.001,最终接近 32 MB,而转到0.0001 将为您带来高达 3.2 GB。即使您有这么多 RAM,也意味着您不再从 CPU 缓存中受益很多东西,这可能会比直接 CPU 成本所显示的更慢。

您的代码非常几乎没有利用numpy 的功能,并且可能会被收紧很多。这样做会节省大量内存,并允许将大部分工作推到单个函数调用中,并在 C 中执行循环,运行速度比 Python 解释器快很多

对于最简单的改进,请使用diff_i。在循环运行之前,您就知道它将有多少元素,并且计算可以简化为简单的数组操作,在外循环开始之前将 j_i 转换为 numpy 数组,并用一个替换循环对j_i 进行的一系列简单计算,直接将diff_i 生成为numpy 数组,根本没有Python 级别的循环。

我还没有测试过这个(在这台机器上没有numpy),但是一个粗略的尝试是在j_i被填充后立即转换它:

j_i = np.array(j_i)

并将diff_i 声明为:

diff_i = np.array()

然后替换:

for i in range(1,(len(time)-l)):
    diff=(j_i[l+i]-j_i[i])**2*dt
    diff_i.append(diff)

与:

new_diff = (j_i[l+1:] - j_i[:-(l+1)]) ** 2 * dt
diff_i = np.concatenate((diff_i, new_diff))

【讨论】:

  • 你的解释真的很清楚很简单。我现在已经解决了我的问题。我接受你的回答,因为它给了我关于执行时间依赖性的准确答案。你能投票给我的问题吗,因为我不能问很多问题,因为声誉。谢谢。
猜你喜欢
  • 2022-01-04
  • 2020-08-13
  • 2018-02-04
  • 1970-01-01
  • 2015-05-03
  • 1970-01-01
  • 2019-10-07
  • 1970-01-01
  • 2016-11-08
相关资源
最近更新 更多