我是python并行计算领域的初学者。我想将我的代码中由循环组成的一部分并行化。默认情况下,我的代码运行time
for循环3年。在每个day
中,我的代码调用一个bash脚本run_offline.sh
并运行它8次。每次bash脚本都会被循环id赋予不同的输入数据索引。以下是我的python代码demo.py
:的主要部分
import os
import numpy as np
from dateutil.rrule import rrule, HOURLY, DAILY
import datetime
import subprocess
...
start_date = datetime.datetime(2017, 1, 1, 10, 0, 0)
end_date = datetime.datetime(2019, 12, 31, 10, 0, 0)
...
loop_id = 0
for date_assim in rrule(freq=HOURLY,
dtstart=start_date,
interval=time_delta,
until=end_date):
RESDIR='./results/'
TYP='experiment_1'
END_ID = 8
YYYYMMDDHH = date_assim.strftime('%Y%m%d%H')
p1 = subprocess.Popen(['./run_offline.sh', str(END_ID), str(loop_id), str(YYYYMMDDHH), RESDIR, TYP])
p1.wait()
#%%
# p1 creates 9 files from RESULTS_0.nc to RESULTS_8.nc
# details please see the bash script attached below
# following are codes computing based on RESULTS_${CID}.nc, CID from 0 to 8.
# In total 9 files are generated by p1 and used later.
loop_id += 1
并且./run_offline.sh
运行大气模型offline.exe
9次,如下所示:
#!/bin/bash
# Usage: ./run_offline.sh END_ID loop_id YYYYMMDDHH RESDIR TYP
END_ID=${1:-1}
loop_id=${2:-1}
YYYYMMDDHH=${3:-1}
RESDIR=${4:-1}
TYP=${5:-1}
END_ID=`echo $((END_ID))`
loop_id=`echo $((loop_id))`
CID=0
ln -sf PREP_0.nc PREP.nc # one of the input file required. Must named by PREP.nc
while [ $CID -le $END_ID ]; do
cp -f ./OPTIONS.nam_${CID} ./OPTIONS.nam # one of the input file required by offline.exe
# different ./OPTIONS.nam_${CID} has different index of a perturbation.
# Say ./OPTIONS.nam_1 lets the offline.exe knows it should perturb the first variable in the atmospheric model,
# ./OPTIONS.nam_2 perturbs the second variable...
./offline.exe
cp RESULTS1.nc RESULTS_${CID}.OUT.nc # for next part of python codes in demo.py
mv RESULTS2.nc $RESDIR/$TYP/RESULTS2_${YYYYMMDDHH}.nc # store this file in my results dir
CID=$((CID+1))
done
现在我发现offline.exe
的for循环非常耗时。每次调用run_offline.sh
时大约是10-20s
(运行./offline.exe
9次需要10-20s(。总的来说,它的平均成本为15s*365*3=4.5hours
,如果我想运行我的脚本3年。。。那么我可以将offline.exe
的循环并行化吗?比如将不同CCD_ 13的不同运行分配给服务器中的不同核心/子进程。但应该注意,当我们每次运行offline.exe
时,两个输入文件OPTIONS.nam
和PREP.nc
被强制命名为相同的名称。。。。这意味着我们不能将CCD_ 17用于循环CCD_。那么,我可以使用dask
或numba
来帮助这种并行化吗?谢谢
如果我正确理解你的问题,你运行了大约1000次bash脚本,它运行了8到9次黑盒可执行文件,而这个可执行文件是主要的瓶颈。
那么我可以并行offline.exe的循环吗?
这很难说,因为可执行文件是一个黑盒。您需要检查程序所需的输入/输出/临时数据。例如,如果程序将临时文件存储在存储设备中的某个位置,那么并行调用它将导致竞争条件。此外,只有当计算部分完全独立时,才能并行调用它。数据流分析对于了解是否可以并行化应用程序(尤其是当它由多个程序组成时(非常有用。
此外,您需要检查程序是否已经并行。并行运行多个并行程序通常会导致执行速度慢得多,原因是需要调度大量线程、缓存使用率低、同步模式差等。
在您的情况下,我认为最好的选择是并行化循环中运行的程序(即offline.exe
(。否则,如果程序是顺序的并且可以并行化(见上文(,那么您可以在bash中使用&
运行多个进程,然后在循环结束时等待它们。或者,您可以使用GNU并行。
但应该注意的是,当我们每次运行offline.exe 时,两个输入文件OPTIONS.nam和PREP.nc被强制命名为相同的名称
这可以通过从N个不同的工作目录并行调用N个程序来解决。如果程序在其工作目录中创建临时文件,这实际上更安全。您需要在并行执行之前以及之后移动/复制文件。
如果程序修改了文件OPTIONS.nam
和/或PREP.nc
,那么这意味着计算是完全连续的,不能并行化(我假设每天的计算取决于前一天,因为这是科学模拟中非常常见的模式(。
那么我可以使用dask或numba来帮助这种并行化吗?
否。Dask和Numba并不意味着要在这种情况下使用。它们被设计为在Python代码中对Numpy数组进行操作。您想要并行化的部分是在bash中,而并行化的程序显然甚至不是用Python编写的。
如果不能使offline.exe
使用OPTIONS.nam和RESULTS1.nc以外的其他名称,则需要确保并行实例不会相互覆盖。
一种方法是为每次运行创建一个目录:
#!/bin/bash
# Usage: ./run_offline.sh END_ID loop_id YYYYMMDDHH RESDIR TYP
END_ID=${1:-1}
loop_id=${2:-1}
YYYYMMDDHH=${3:-1}
RESDIR=${4:-1}
TYP=${5:-1}
END_ID=`echo $((END_ID))`
loop_id=`echo $((loop_id))`
doit() {
mkdir $1
cd $1
ln -sf ../PREP_$1.nc PREP.nc
cp -f ./OPTIONS.nam_$1 ./OPTIONS.nam # one of the input file required by
../offline.exe
cp RESULTS1.nc ../RESULTS_$1.OUT.nc # for next part of python codes in demo.py
mv RESULTS2.nc $RESDIR/$TYP/RESULTS2_${YYYYMMDDHH}.nc # store this file in my results dir
}
export -f doit
export RESDIR YYYYMMDDHH TYP
seq 0 $END_ID | parallel doit