簡體   English   中英

Python:並行化嵌套 for 循環

[英]Python: Parallelize nested for loop

我在 python 中嵌套了 for 循環來創建 netCDF 文件。 for 循環采用 pandas dataframe 和時間、緯度、地段和參數,並將 netCDF 文件中的信息替換為正確位置和時間的參數。 這花費了太長時間,因為 pandas dataframe 有超過 80000 行,而 netCDF 文件有大約 8000 個時間步長。 我一直在尋找使用xargsmultiprocessing但在第一種情況下使用文件作為輸入,在第二種情況下,它產生與我使用的進程一樣多的輸出。 我沒有並行處理的經驗,所以我的斷言可能完全錯誤。 這是我正在使用的代碼:

with Dataset(os.path.join('Downloads', inv, 'observations.nc'), 'w') as dset:
    
    dset.createDimension('time_components', 6)
    
    groups = ['obs', 'mix_apri', 'mix_apos', 'mix_background']
    
    for group in groups:
        
        dset.createGroup(group)
        dset[group].createDimension('nt', 8760)
        dset[group].createDimension('nlat', 80)
        dset[group].createDimension('nlon', 100)
        
        times_start = dset[group].createVariable('times_start', 'i4', ('nt', 'time_components'))
        times_end = dset[group].createVariable('times_end', 'i4', ('nt', 'time_components'))
        lats = dset[group].createVariable('lats', 'f4', ('nlat'))
        lons = dset[group].createVariable('lons', 'f4', ('nlon'))
        
        times_start[:,:] = list(emis_apri['biosphere']['times_start'])
        times_end[:,:] = list(emis_apri['biosphere']['times_end'])
        lats[:] = list(emis_apri['biosphere']['lats'])
        lons[:] = list(emis_apri['biosphere']['lons'])
        
    conc_obs = dset['obs'].createVariable('conc', 'f8', ('nt', 'nlat', 'nlon'))
    conc_mix_apri = dset['mix_apri'].createVariable('conc', 'f8', ('nt', 'nlat', 'nlon'))
    conc_mix_apos = dset['mix_apos'].createVariable('conc', 'f8', ('nt', 'nlat', 'nlon'))
    conc_mix_background = dset['mix_background'].createVariable('conc', 'f8', ('nt', 'nlat', 'nlon'))
    
    for i in range(8760):
        conc_obs[i,:,:] = emis_apri['biosphere']['emis'][i][:,:]*0
        
    conc_mix_apri[:,:,:] = list(conc_obs)
    conc_mix_apos[:,:,:] = list(conc_obs)
    conc_mix_background[:,:,:] = list(conc_obs)
    
    db = obsdb(os.path.join('Downloads', inv, 'observations.apos.tar.gz'))
    nsites = db.sites.shape[0]
    for isite, site in enumerate(db.sites.itertuples()):
        dbs = db.observations.loc[db.observations.site == site.Index]
        lat = where((array(emis_apri['biosphere']['lats']) >= list(dbs.lat)[0]-0.25) & (array(emis_apri['biosphere']['lats']) <= list(dbs.lat)[0]+0.25))[0][0]
        lon = where((array(emis_apri['biosphere']['lons']) >= list(dbs.lon)[0]-0.25) & (array(emis_apri['biosphere']['lons']) <= list(dbs.lon)[0]+0.25))[0][0]
        for i in range(len(list(dbs.time))):
            for j in range(len(times_start)):
                if datetime(*times_start[j,:].data) >= Timestamp.to_pydatetime(list(dbs.time)[i]) and datetime(*times_end[j,:].data) >= Timestamp.to_pydatetime(list(dbs.time)[i]):
                    conc_obs[i,lat,lon] = list(dbs.obs)[i]
                    conc_mix_apri[i,lat,lon] = list(dbs.mix_apri)[i]
                    conc_mix_apos[i,lat,lon] = list(dbs.mix_apos)[i]
                    conc_mix_background[i,lat,lon] = list(dbs.mix_background)[i]

for isite, site in enumerate(db.sites.itertuples()):是我需要並行化的代碼部分。 我真的很感激對此的任何見解。

將以下內容視為偽代碼,因為我無法在沒有任何樣本的情況下運行任何測試等。我通常使用 mpi4py 並行化我的代碼,在你的情況下,你可以在一開始就這樣做:

from mpi4py import MPI
comm = MPI.COMM_WORLD
size = comm.Get_size(); # let your program know how many processors you are using
rank = comm.Get_rank() # let the running program know, which processor it is

現在,在代碼的開頭,讓一個進程成為所謂的主任務,它可以完成所有任務不能同時完成的所有基本/重要的事情。 例如,打開/初始化 output 的某些文件。 因此,在您的代碼中,對於這些部分,您可以使用:

if rank==0:
    # do some important stuff
else:
    # do something not important (for example a = 5)
comm.barrier() # this is important to synchronize the  processes

現在,要並行化您的代碼,您可以在分布式 db.sites 上執行循環,即您將 db.sites.itertuples() 除以您要使用的處理器數量:

allsites = db.sites.itertuples() # all the processor have to know all the sites
sites = allsites[rank::size] # each starts from it's current rank and jumps with the size

for isite, site in enumerate(sites):
    dbs = db.observations.loc[db.observations.site == site.Index]
    lat = where((array(emis_apri['biosphere']['lats']) >= list(dbs.lat)[0]-0.25) & (array(emis_apri['biosphere']['lats']) <= list(dbs.lat)[0]+0.25))[0][0]
    lon = where((array(emis_apri['biosphere']['lons']) >= list(dbs.lon)[0]-0.25) & (array(emis_apri['biosphere']['lons']) <= list(dbs.lon)[0]+0.25))[0][0]
    for i in range(len(list(dbs.time))):
        for j in range(len(times_start)):
            if datetime(*times_start[j,:].data) >= Timestamp.to_pydatetime(list(dbs.time)[i]) and datetime(*times_end[j,:].data) >= Timestamp.to_pydatetime(list(dbs.time)[i]):
                conc_obs[i,lat,lon] = list(dbs.obs)[i]
                conc_mix_apri[i,lat,lon] = list(dbs.mix_apri)[i]
                conc_mix_apos[i,lat,lon] = list(dbs.mix_apos)[i]
                conc_mix_background[i,lat,lon] = list(dbs.mix_background)[i]

comm.barrier() # do not forget to synchronize

然而,在這種情況下,“isite”現在具有基於列表大小的值,您正在放棄。因此,它不是 0...len(allsites),而是 0...len(allsites)/size . 如果“isite”對於具有從 0 到 len(allsites) 的值很重要,則您必須以某種方式重新計算。 也許 isite_global = isite*size+rank 以獲得處理器正在執行的實際數量。

所以,到底如何運行代碼,我通常會這樣做:

mpiexec -np 10 ipython script_name

在終端上運行 10 個處理器上的代碼。

但是,無論如何,最困難的部分是在沒有庫的特定支持的情況下並行化 I/O 操作。 我不確定 netCDF4 是否支持並行 I/O,這意味着如果您的處理器等級為 0...X 為 X 處理器同時打開文件,將某些內容寫入文件中的特定位置並關閉文件,然后來自所有處理器寫在那里。

因此,最安全的想法是讓一個處理器(主)負責 output 並在寫入之前交換/收集需要從所有子處理器寫入的數據。

希望這會有所幫助,祝代碼好運!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM