【问题标题】:How can I make my python code run faster如何让我的 python 代码运行得更快
【发布时间】:2026-01-27 19:55:01
【问题描述】:

我正在编写循环多个 netcdf 文件(大 ~28G)的代码。 netcdf 文件在整个域中具有多个 4D 变量[时间、东西、南北、高度]。目标是遍历这些文件并遍历域中所有这些变量的每个位置,并提取某些变量以存储到一个大数组中。当文件丢失或不完整时,我用 99.99 填充这些值。现在我只是通过循环超过 2 个每日 netcdf 文件进行测试,但由于某种原因,它需要永远(约 14 小时)。我不确定是否有办法优化这段代码。我不认为 python 应该花这么长时间来完成这项任务,但也许这是 python 或我的代码的问题。以下是我的代码,希望它是可读的,非常感谢任何关于如何使其更快的建议:

#Domain to loop over
k_space = np.arange(0,37)
j_space = np.arange(80,170)
i_space = np.arange(200,307)

predictors_wrf=[]
names_wrf=[]

counter = 0
cdate = start_date
while cdate <= end_date:
    if cdate.month not in month_keep:
        cdate+=inc
        continue
    yy = cdate.strftime('%Y')        
    mm = cdate.strftime('%m')
    dd = cdate.strftime('%d')
    filename = wrf_path+'\wrfoutRED_d01_'+yy+'-'+mm+'-'+dd+'_'+hour_str+'_00_00'
    for i in i_space:
        for j in j_space:
            for k in k_space:
                    if os.path.isfile(filename):
                        f = nc.Dataset(filename,'r')
                        times = f.variables['Times'][1:]
                        num_lines = times.shape[0]
                        if num_lines == 144:
                            u = f.variables['U'][1:,k,j,i]
                            v = f.variables['V'][1:,k,j,i]
                            wspd = np.sqrt(u**2.+v**2.)
                            w = f.variables['W'][1:,k,j,i]
                            p = f.variables['P'][1:,k,j,i]
                            t = f.variables['T'][1:,k,j,i]
                        if num_lines < 144:
                            print "partial files for WRF: "+ filename
                            u = np.ones((144,))*99.99
                            v = np.ones((144,))*99.99
                            wspd = np.ones((144,))*99.99
                            w = np.ones((144,))*99.99
                            p = np.ones((144,))*99.99
                            t = np.ones((144,))*99.99
                    else:
                        u = np.ones((144,))*99.99
                        v = np.ones((144,))*99.99
                        wspd = np.ones((144,))*99.99
                        w = np.ones((144,))*99.99
                        p = np.ones((144,))*99.99
                        t = np.ones((144,))*99.99
                        counter=counter+1
                    predictors_wrf.append(u)
                    predictors_wrf.append(v)
                    predictors_wrf.append(wspd)
                    predictors_wrf.append(w)
                    predictors_wrf.append(p)
                    predictors_wrf.append(t)
                    u_names = 'u_'+str(k)+'_'+str(j)+'_'+str(i)
                    v_names = 'v_'+str(k)+'_'+str(j)+'_'+str(i)
                    wspd_names = 'wspd_'+str(k)+'_'+str(j)+'_'+str(i)
                    w_names = 'w_'+str(k)+'_'+str(j)+'_'+str(i)
                    p_names = 'p_'+str(k)+'_'+str(j)+'_'+str(i)
                    t_names = 't_'+str(k)+'_'+str(j)+'_'+str(i)
                    names_wrf.append(u_names)
                    names_wrf.append(v_names)
                    names_wrf.append(wspd_names)
                    names_wrf.append(w_names)
                    names_wrf.append(p_names)
                    names_wrf.append(t_names)
    cdate+=inc

【问题讨论】:

  • 您可以使用多处理同时处理文件。为不同的进程安排 k,j,i 空间,让它们各自完成自己的任务
  • 什么是nc.Dataset?此外,在提高速度之前,您需要知道它为什么慢。您需要分析您的代码并测量
  • 这是我使用 python 读取 netcdf 文件的方式 我在前面的代码中有一个语句,此处未显示:import netCDF4 as nc
  • 下面的多核建议会有所帮助。此外,如果您在 iPython notebook 中工作,将其写入从命令行运行的脚本可以极大地加快速度。 28GB 是一个巨大的文件。如果两个文件都在这个大小范围内,并且您有 3 个带条件的循环,那么在单个内核上运行 14 小时并不是不存在的,无论它看起来多么荒谬。 R 比 Python 慢得多,较小的文件需要 8 到 12 次才能通过更少的循环进行排序。只要尽可能保守地处理冗余操作并启动更多内核!
  • 您似乎多次浏览文件,{f.variables['Times'][1:]} 浏览文件以搜索这些var。这是针对每个循环完成的。这样做一次,而不是在每个循环中。

标签: python performance loops timing


【解决方案1】:

这是收紧forloops 的蹩脚的第一步。由于每个文件只使用一次文件形状,因此可以将处理移到循环之外,这应该减少中断处理中的数据加载量。我仍然不明白counterinc 做了什么,因为它们似乎没有在循环中更新。您肯定想研究重复的字符串连接性能,或者附加到 predictors_wrfnames_wrf 的性能如何作为起点

k_space = np.arange(0,37)
j_space = np.arange(80,170)
i_space = np.arange(200,307)

predictors_wrf=[]
names_wrf=[]

counter = 0
cdate = start_date
while cdate <= end_date:
    if cdate.month not in month_keep:
        cdate+=inc
        continue
    yy = cdate.strftime('%Y')        
    mm = cdate.strftime('%m')
    dd = cdate.strftime('%d')
    filename = wrf_path+'\wrfoutRED_d01_'+yy+'-'+mm+'-'+dd+'_'+hour_str+'_00_00'
    file_exists = os.path.isfile(filename)
    if file_exists:
        f = nc.Dataset(filename,'r')
        times = f.variables['Times'][1:]
        num_lines = times.shape[0]
    for i in i_space:
        for j in j_space:
            for k in k_space:
                    if file_exists:    
                        if num_lines == 144:
                            u = f.variables['U'][1:,k,j,i]
                            v = f.variables['V'][1:,k,j,i]
                            wspd = np.sqrt(u**2.+v**2.)
                            w = f.variables['W'][1:,k,j,i]
                            p = f.variables['P'][1:,k,j,i]
                            t = f.variables['T'][1:,k,j,i]
                        if num_lines < 144:
                            print "partial files for WRF: "+ filename
                            u = np.ones((144,))*99.99
                            v = np.ones((144,))*99.99
                            wspd = np.ones((144,))*99.99
                            w = np.ones((144,))*99.99
                            p = np.ones((144,))*99.99
                            t = np.ones((144,))*99.99
                    else:
                        u = np.ones((144,))*99.99
                        v = np.ones((144,))*99.99
                        wspd = np.ones((144,))*99.99
                        w = np.ones((144,))*99.99
                        p = np.ones((144,))*99.99
                        t = np.ones((144,))*99.99
                        counter=counter+1
                    predictors_wrf.append(u)
                    predictors_wrf.append(v)
                    predictors_wrf.append(wspd)
                    predictors_wrf.append(w)
                    predictors_wrf.append(p)
                    predictors_wrf.append(t)
                    u_names = 'u_'+str(k)+'_'+str(j)+'_'+str(i)
                    v_names = 'v_'+str(k)+'_'+str(j)+'_'+str(i)
                    wspd_names = 'wspd_'+str(k)+'_'+str(j)+'_'+str(i)
                    w_names = 'w_'+str(k)+'_'+str(j)+'_'+str(i)
                    p_names = 'p_'+str(k)+'_'+str(j)+'_'+str(i)
                    t_names = 't_'+str(k)+'_'+str(j)+'_'+str(i)
                    names_wrf.append(u_names)
                    names_wrf.append(v_names)
                    names_wrf.append(wspd_names)
                    names_wrf.append(w_names)
                    names_wrf.append(p_names)
                    names_wrf.append(t_names)
    cdate+=inc

【讨论】:

    【解决方案2】:

    对于您的问题,我认为multiprocessing 会很有帮助。我浏览了你的代码,并在这里有一些建议。

    1. 不使用开始时间,而是使用文件名作为代码中的迭代器。

      包装一个函数以根据时间找出所有文件名并返回所有文件名的列表。

      def fileNames(start_date, end_date):
          # Find all filenames.
          cdate = start_date
          fileNameList = [] 
          while cdate <= end_date:
              if cdate.month not in month_keep:
                  cdate+=inc
                  continue
              yy = cdate.strftime('%Y')        
              mm = cdate.strftime('%m')
              dd = cdate.strftime('%d')
              filename = wrf_path+'\wrfoutRED_d01_'+yy+'-'+mm+'-'+dd+'_'+hour_str+'_00_00'
              fileNameList.append(filename)
              cdate+=inc
      
          return fileNameList
      
    2. 包装提取数据的代码并填写 99.99,函数的输入是文件名。

      def dataExtraction(filename):
          file_exists = os.path.isfile(filename)
          if file_exists:
             f = nc.Dataset(filename,'r')
             times = f.variables['Times'][1:]
             num_lines = times.shape[0]
          for i in i_space:
              for j in j_space:
                  for k in k_space:
                      if file_exists:    
                          if num_lines == 144:
                              u = f.variables['U'][1:,k,j,i]
                              v = f.variables['V'][1:,k,j,i]
                              wspd = np.sqrt(u**2.+v**2.)
                              w = f.variables['W'][1:,k,j,i]
                              p = f.variables['P'][1:,k,j,i]
                              t = f.variables['T'][1:,k,j,i]
                          if num_lines < 144:
                              print "partial files for WRF: "+ filename
                              u = np.ones((144,))*99.99
                              v = np.ones((144,))*99.99
                              wspd = np.ones((144,))*99.99
                              w = np.ones((144,))*99.99
                              p = np.ones((144,))*99.99
                              t = np.ones((144,))*99.99
                          else:
                              u = np.ones((144,))*99.99
                              v = np.ones((144,))*99.99
                              wspd = np.ones((144,))*99.99
                              w = np.ones((144,))*99.99
                              p = np.ones((144,))*99.99
                              t = np.ones((144,))*99.99
                              counter=counter+1
                          predictors_wrf.append(u)
                          predictors_wrf.append(v)
                          predictors_wrf.append(wspd)
                          predictors_wrf.append(w)
                          predictors_wrf.append(p)
                          predictors_wrf.append(t)
                          u_names = 'u_'+str(k)+'_'+str(j)+'_'+str(i)
                          v_names = 'v_'+str(k)+'_'+str(j)+'_'+str(i)
                          wspd_names = 'wspd_'+str(k)+'_'+str(j)+'_'+str(i)
                          w_names = 'w_'+str(k)+'_'+str(j)+'_'+str(i)
                          p_names = 'p_'+str(k)+'_'+str(j)+'_'+str(i)
                          t_names = 't_'+str(k)+'_'+str(j)+'_'+str(i)
                          names_wrf.append(u_names)
                          names_wrf.append(v_names)
                          names_wrf.append(wspd_names)
                          names_wrf.append(w_names)
                          names_wrf.append(p_names)
                          names_wrf.append(t_names)
      
      
          return zip(predictors_wrf, names_wrf)
      
    3. 使用多处理来完成您的工作。通常,所有计算机都有超过 1 个 CPU 内核。当有大量 CPU 计算时,多处理将有助于提高速度。根据我之前的经验,多处理最多可以减少 2/3 的处理大型数据集的时间。

      更新:在 2017 年 2 月 25 日再次测试我的代码和文件后,我发现使用 8 核处理庞大的数据集节省了 90% 的崩溃时间。

      if __name__ == '__main__':
            from multiprocessing import Pool  # This should be in the beginning statements.
            start_date = '01-01-2017'
            end_date = '01-15-2017'
            fileNames = fileNames(start_date, end_date)
            p = Pool(4) # the cores numbers you want to use.
            results = p.map(dataExtraction, fileNames)
            p.close()
            p.join()
      
    4. 最后,请注意这里的数据结构,因为它非常复杂。希望这可以帮助。如果您有任何其他问题,请留下 cmets。

    【讨论】:

      【解决方案3】:

      我没有太多建议,但有几点需要注意。

      不要多次打开该文件

      首先,您定义这个filename 变量,然后在这个循环内部(内部深处:三个for 循环深),您正在检查文件是否存在并可能在那里打开它(我不知道nc.Dataset可以,但我猜它必须打开文件并读取它):

      filename = wrf_path+'\wrfoutRED_d01_'+yy+'-'+mm+'-'+dd+'_'+hour_str+'_00_00'
          for i in i_space:
              for j in j_space:
                  for k in k_space:
                          if os.path.isfile(filename):
                              f = nc.Dataset(filename,'r')
      

      这将是非常低效的。如果文件在所有循环之前没有更改,您当然可以打开一次。

      尽量少用for循环

      所有这些嵌套的 for 循环都增加了您需要执行的操作数量。一般建议:尝试改用 numpy 操作。

      使用 CProfile

      如果您想知道为什么您的程序需要很长时间,最好的找出方法之一就是分析它们。

      【讨论】: