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