如何在群集上分发执行

时间:2015-09-16 05:02:29

标签: parallel-processing gnu-parallel

我正在做研究,我经常需要使用不同的输入(重复输入的每个组合)执行相同的程序并存储结果,以进行聚合。

我想通过在多台机器上并行执行这些实验来加快这一过程。但是,我想避免手动启动它们的麻烦。此外,我希望我的程序可以作为单个线程实现,并且只在其上添加并行化。

我使用Ubuntu计算机,所有计算机都可以在本地网络中访问。 我知道GNU Parallel可以解决这个问题,但我不熟悉它。有人可以帮助我为我的实验设置环境吗?

1 个答案:

答案 0 :(得分:1)

请注意,这个答案已经从我的一个脚本改编而来,未经测试。如果您发现错误,欢迎您编辑答案。

首先,为了使流程完全批量化,我们需要一个非交互式SSH登录(GNU Parallel用于远程启动命令的那些)。

为此,首先生成一对RSA密钥(如果您还没有),其中包含:

ssh-keygen -t rsa

将生成一对私钥和公钥,默认存储在~/.ssh/id_rsa~/.ssh/id_rsa.pub中。使用这些位置很重要,因为openssh会在这里寻找它们。虽然openssh命令允许您指定私钥文件(通过-i PRIVATE_KEY_FILE_PATH传递),但GNU Parallel没有这样的选项。

接下来,我们需要复制我们将要使用的所有远程计算机上的公钥。对于群集中的每台计算机(我将称之为#34; worker"),在本地计算机上运行此命令:

ssh-copy-id -i ~/.ssh/id_rsa.pub WORKER_USER@WORKER_HOST

此步骤是交互式的,因为您需要通过用户ID和密码登录每个工作人员。

从此刻起,从您的客户端登录到每个工作人员都是非交互式的。接下来,让我们使用逗号分隔的工作列表设置bash变量。我们将使用GNU Parallel特殊语法进行设置,该语法允许指示每个worker使用多少CPU:

WORKERS_PARALLEL="2/user1@192.168.0.10,user2@192.168.0.20,4/user3@10.0.111.69"

在这里,我指定在192.168.0.10上我只想要2个并行进程,而在10.0.111.69我想要。至于192.168.0.20,因为我没有指定任何数字,GNU Parallel会计算出远程机器有多少CPU(实际上是CPU内核)并执行那么多并行进程。

由于我还需要一个openssh可以理解的格式的相同列表,我将创建一个没有CPU信息和空格而不是逗号的第二个变量。我自动执行此操作:

WORKERS=`echo $WORKERS_PARALLEL | sed 's/[0-9]*\///g' | sed 's/,/ /g'`

现在是时候设置我的代码了。我假设每个worker都配置为运行我的代码,所以我只需要复制代码。在工作者上,我通常在/ tmp文件夹中工作,所以下面假设。代码将通过SSH隧道复制并远程提取:

WORKING_DIR=/tmp/myexperiments
TAR_PATH=/tmp/code.tar.gz
# Clean from previous executions
parallel --nonall -S $WORKERS rm -rf $WORKING_DIR $TAR_PATH
# Copy the the tar.gz file on the worker
parallel scp LOCAL_TAR_PATH {}:/tmp ::: `echo $WORKERS`
# Create the working directory on the worker
parallel --nonall -S $WORKERS mkdir -p $WORKING_DIR
# Extract the tar file in the working directory
parallel --nonall -S $WORKERS tar --warning=no-timestamp -xzf $TAR_PATH -C $WORKING_DIR

请注意,同一台计算机上的多次执行将使用相同的工作目录。我假设在特定时间只运行一个版本的代码;如果不是这种情况,则需要修改命令以使用不同的工作目录。 我使用--warning=no-timestamp指令来避免在机器时间超过工人时间时发出的恼人警告。

我们现在需要在本地机器中创建用于存储运行结果的目录,每个实验组一个(即具有相同参数的多次执行)。在这里,我使用两个虚拟参数alpha和beta:

GROUP_DIRS="results/alpha=1,beta=1 results/alpha=0.5,beta=1 results/alpha=0.2,beta=0.5"
N_GROUPS=3
parallel --header : mkdir -p {DIR} ::: DIR $GROUP_DIRS

请注意,这里使用parallel不是必需的:使用循环会有效,但我发现它更具可读性。我还存储了组的数量,我们将在下一步中使用它们。

最后的准备步骤包括创建将在实验中使用的所有参数组合的列表,每个参数组合根据需要重复多次。每次重复都会附加一个增量编号,用于识别不同的运行。

ALPHAS="1.0 0.5 0.2"
BETAS="1.0 1.0 0.5"
REPETITIONS=1000
PARAMS_FILE=/tmp/params.txt
# Create header
echo REP GROUP_DIR ALPHA BETA > $PARAMS_FILE
# Populate
parallel \
  --header : \
  --xapply \
  if [ ! -e {GROUP_DIR}"exp"{REP}".dat" ]';' then echo {REP} {GROUP_DIR} {ALPHA} {BETA} '>>' $PARAMS_FILE ';' fi \
  ::: REP $(for i in `seq $REPETITIONS`; do printf $i" %.0s" $(seq $N_GROUPS) ; done) \
  ::: GROUP_DIR $GROUP_DIRS \
  ::: ALPHA $ALPHAS \
  ::: BETA $BETAS

在这一步中,我还实现了一个控件:如果.dat文件已经存在,我会跳过这组参数。这是从实践中产生的:我经常中断GNU Parallel的执行,后来决定通过重新执行这些命令来恢复它。通过这种简单的控制,我避免运行超过必要的实验。

现在我们终于可以进行实验了。此示例中的算法生成我想要检索的参数--save-data中指定的文件。我还想将stdout和stderr保存在文件中,以便进行调试。

cat $PARAMS_FILE | parallel \
  --sshlogin $WORKERS_PARALLEL \
  --workdir $WORKING_DIR \
  --return {GROUP_DIR}"exp"{REP}".dat" \
  --return {GROUP_DIR}"exp"{REP}".txt" \
  --cleanup \
  --xapply \
  --header 1 \
  --colsep " " \
  mkdir -p {TEST_DIR} ';' \
 ./myExperiment \
  --random-seed {REP} \
  --alpha {ALPHA} \
  --beta {BETA} \
  --save-data {GROUP_DIR}"exp"{REP}".dat" \
  '&>' {GROUP_DIR}"exp"{REP}".txt"

关于参数的一点解释。 --sshlogin(可以缩写为-S)传递Parallel将用于分配计算负载的worker列表。 --workdir设置Parallel的工作目录,默认为~--return指令在执行完成后复制指定的文件。 --cleanup删除复制的文件。 --xapply告诉Parallel将参数解释为元组(而不是设置乘以笛卡尔积)。 --header 1告诉Parallel,参数文件的第一行必须被解释为header(其条目将用作列的名称)。 --colsep告诉Parallel,参数文件中的列是以空格分隔的。

警告:Ubuntu的并行版本已过时(2013年)。特别是,有一个错误阻止上面的代码正常运行,这已经在几天前修复。要获取最新的每月快照,请运行(不需要root权限):

(wget -O - pi.dk/3 || curl pi.dk/3/ || fetch -o - http://pi.dk/3) | bash

请注意,上面提到的错误的修复程序将仅包含在2015年9月22日的下一个快照中。如果您赶时间,则应手动安装吸烟最热的。

最后,清洁我们的工作环境是一个好习惯:

rm $PARAMS_FILE
parallel --nonall -S $WORKERS rm -rf $WORKING_DIR $TAR_PATH

如果您将其用于研究并发表论文,请记住引用Ole Tange的原创作品(参见parallel --bibtex)。