前言

本文介绍了GNU parallel的Linux安装及主要使用方法,为实现CPU并行处理任务做准备。


一、GNU parallel是什么?

在这里插入图片描述

官方解释: 来源:https://www.cnblogs.com/achievegoals/p/9263506.html
GNU Parallel是一个shell工具,为了在一台或多台计算机上并行的执行计算任务,一个计算任务可以是一条shell命令或者一个以每一行做为输入的脚本程序。通常的输入是文件列表、主机列表、用户列表、URL列表或者表格列表;一个计算任务也可以是一个从管道读取的一条命令。GNU Parallel会把输入分块,然后通过管道并行的执行。

如果你会使用xargs和tee命令,你会发现GNU Parallel非常易于使用,因为GNU Parallel具有与xargs一样的选项。GNU Parallel可以替代大部分的shell循环,并且用并行的方式更快的完成计算任务。

GNU Parallel保证它的输出与顺序执行计算任务时是一样的,这样就可以方便的把GNU Parallel的输出做为其它程序的输入。

对于每一行输入,GNU Parallel会把这一行做为参数来运行指定的命令。如果没有给出命令,那么这一行会被当做命令执行。多行输入会并行的运行。GNU Parallel经常被用于替代xargs或者cat | bash。

另一种更直观的解释:来源:https://www.yingsoo.com/news/posts/58584.html
GNU Parallel is a general parallelizer and makes is easy to run jobs in parallel on the same machine or on multiple machines you have ssh access to.
If you have 32 different jobs you want to run on 4 CPUs, a straight forward way to parallelize is to run 8 jobs on each CPU:
在这里插入图片描述
GNU Parallel instead spawns a new process when one finishes - keeping the CPUs active and thus saving time:
在这里插入图片描述

二、安装步骤

1.Linux系统上安装及准备

选择其中一种安装方式。
方式一:系统直接安装

# Debian/Ubuntu系统上安装:
sudo apt install parallel
# 在Red Hat/CentOS系统上安装:
sudo yum install parallel

方式二:安装最新版

(wget -O - pi.dk/3 || curl pi.dk/3/) | bash

方式三:这条命令同时也会安装最新版的指南

man parallel_tutorial

方式四:编译安装最新版

wget https://ftpmirror.gnu.org/parallel/parallel-latest.tar.bz2
tar xjf parallel-latest.tar.bz2
cd parallel*
./configure --install=/path/to/install
make
make install

三、parallel使用介绍

本教程的大部分内容同时也兼容旧版本。

abc-file

生成文件:

parallel -k echo ::: A B C > abc-file

def-file

生成文件:

parallel -k echo ::: D E F > def-file

abc0-file

生成文件:

perl -e 'printf "A\0B\0C\0"' > abc0-file

abc_-file

生成文件:

perl -e 'printf "A_B_C_"' > abc_-file

tsv_file.tsv

生成文件:

perl -e 'printf "f1\tf2\nA\tB\nC\tD\n"' > tsv-file.tsv

num30000

生成文件:

perl -e 'for(1..30000){print "$_\n"}' > num30000

num1000000

生成文件:

perl -e 'for(1..1000000){print "$_\n"}' > num1000000

num_%header

生成文件:

(echo %head1; echo %head2; perl -e 'for(1..10){print "$_\n"}') > num_%header

远程执行:ssh免密码登录 S E R V E R 1 和 SERVER1和 SERVER1SERVER2

生成文件:

SERVER1=server.example.com
SERVER2=server2.example.net

最后应该成功运行如下命令:

ssh $SERVER1 echo works
ssh $SERVER2 echo works

使用 ssh-keygen -t dsa; ssh-copy-id $SERVER1 建立环境(使用empty pass phrase)

2. parallel主要功能介绍

输入源

GNU Parallel的输入源支持文件、命令行和标准输入(stdin或pipe)

单个输入源

从命令行读取输入:

parallel echo ::: A B C

输出(由于任务以并行的方式执行,顺序可能会有所不同):

A
B
C

文件做为输入源:

parallel -a abc-file echo

输出同上。

STDIN(标准输入)做为输入源:

cat abc-file | parallel echo

输出同上。

多输入源

GNU Parallel支持通过命令行指定多个输入源,它会生成所有的组合:

parallel echo ::: A B C ::: D E F

输出:

A D
A E
A F
B D
B E
B F
C D
C E
C F

多个文件做为输入源:

parallel -a abc-file -a def-file echo

输出同上。

STDIN(标准输入)可以做为输入源中的一个,使用“-”:

cat abc-file | parallel -a - -a def-file echo

输出同上。

可以使用“::::”替代 -a:

cat abc-file | parallel echo :::: - def-file

输出同上。

::: 和 :::: 可以混合使用:

parallel echo ::: A B C :::: def-file

输出同上。

适配参数

–xapply 从每一个输入源取一个参数:

parallel --xapply echo ::: A B C ::: D E F

输出:

A D
B E
C F

如果其中一个输入源的长度比较短,它的值会被重复:

parallel --xapply echo ::: A B C D E ::: F G

输出:

A F
B G
C F
D G
E F

改变参数分隔符

GNU Parallel可以指定分隔符替代 ::: 或 ::::,当这两个符号被其它命令占用的时候会特别有用:

parallel --arg-sep ,, echo ,, A B C :::: def-file

输出:

A D
A E
A F
B D
B E
B F
C D
C E
C F

改变参数分隔符:

parallel --arg-file-sep // echo ::: A B C // def-file

输出同上。

改变参数定界符

GNU Parallel默认把一行做为一个参数:使用 \n 做为参数定界符。可以使用 -d 改变:

parallel -d _ echo :::: abc_-file

输出:

A
B
C

\0 代表NULL:

parallel -d '\0' echo :::: abc0-file

输出同上。 -0 是 -d ‘\0’ 的简写(通常用于从 find … -print0读取输入):

parallel -0 echo :::: abc0-file

输出同上。

输入源中的结束值

GNU Parallel支持指定一个值做为结束标志:

parallel -E stop echo ::: A B stop C D

输出:

A
B

跳过空行

使用 --no-run-if-empty 来跳过空行:

(echo 1; echo; echo 2) | parallel --no-run-if-empty echo

输出:

1
2

构建命令行

没有指定命令意味着参数就是命令

如果parallel之后没有给定命令,那么这些参数会被当做命令:

parallel ::: ls 'echo foo' pwd

输出:

[当前文件列表]
foo
[当前工作目录的路径]

命令可以是一个脚本文件,一个二进制可执行文件或一个bash的函数(须用 export -f 导出函数):

# Only works in Bash and only if $SHELL=.../bash
my_func() {
  echo in my_func $1
}
export -f my_func
parallel my_func ::: 1 2 3

输出:

in my_func 1
in my_func 2
in my_func 3

替换字符串

5种替换字符串

GNU Parallel支持多种替换字符串。默认使用 {}:

parallel echo ::: A/B.C

输出:

A/B.C

指定 {} :

parallel echo {} ::: A/B.C

输出同上 去掉扩展名 {.}:

parallel echo {.} ::: A/B.C

输出

A/B

去掉路径 {/}:

parallel echo {/} ::: A/B.C

输出:

B.C

只保留路径 {//}:

parallel echo {//} ::: A/B.C

输出:

A

去掉路径和扩展名 {/.}:

parallel echo {/.} ::: A/B.C

输出:

B

输出任务编号:

parallel echo {#} ::: A/B.C

输出:

1
2
3

改变替换字符串

使用 -I 改变替换字符串符号 {}:

parallel -I ,, echo ,, ::: A/B.C

输出:

A/B.C

–extensionreplace替换 {.}:

parallel --extensionreplace ,, echo ,, ::: A/B.C

输出:

A/B

–basenamereplace替换 {/}:

parallel --basenamereplace ,, echo ,, ::: A/B.C

输出:

B.C

–dirnamereplace替换 {//}:

parallel --dirnamereplace ,, echo ,, ::: A/B.C

输出:

A

–basenameextensionreplace替换 {/.}:

parallel --basenameextensionreplace ,, echo ,, ::: A/B.C

输出:

B

–seqreplace替换 {#}:

parallel --seqreplace ,, echo ,, ::: A B C

输出:

1
2
3

指定位置替换字符串

如果有多个输入源时,可以通过 {编号} 指定某一个输入源的参数:

parallel echo {1} and {2} ::: A B ::: C D

输出:

A and C
A and D
B and C
B and D

可以使用 / // /. 和 .: 改变指定替换字符串:

parallel echo /={1/} //={1//} /.={1/.} .={1.} ::: A/B.C D/E.F

输出:

/=B.C //=A /.=B .=A/B
/=E.F //=D /.=E .=D/E

位置可以是负数,表示倒着数:

parallel echo 1={1} 2={2} 3={3} -1={-1} -2={-2} -3={-3} ::: A B ::: C D ::: E F

输出:

1=A 2=C 3=E -1=E -2=C -3=A
1=A 2=C 3=F -1=F -2=C -3=A
1=A 2=D 3=E -1=E -2=D -3=A
1=A 2=D 3=F -1=F -2=D -3=A
1=B 2=C 3=E -1=E -2=C -3=B
1=B 2=C 3=F -1=F -2=C -3=B
1=B 2=D 3=E -1=E -2=D -3=B
1=B 2=D 3=F -1=F -2=D -3=B

按列输入

使用 --colsep 把文件中的行切分为列,做为输入参数。下面使用TAB(\t):

1=f1 2=f2
1=A 2=B
1=C 2=D

指定参数名

使用 --header 把每一行输入中的第一个值做为参数名:

parallel --header : echo f1={f1} f2={f2} ::: f1 A B ::: f2 C D

输出:

f1=A f2=C
f1=A f2=D
f1=B f2=C
f1=B f2=D

使用 --colsep 处理使用TAB做为分隔符的文件:

parallel --header : --colsep '\t' echo f1={f1} f2={f2} :::: tsv-file.tsv

输出:

f1=A f2=B
f1=C f2=D

多参数

–xargs 让GNU Parallel支持一行多个参数(可以指定上限):

cat num30000 | parallel --xargs echo | wc -l

输出:

2

30000个参数被分为两行。 一行中的参数个数的上限通过 -s 指定。下面指定最大长度是10000,会被分为17行:

cat num30000 | parallel --xargs -s 10000 echo | wc -l

输出:

17

为了获得更好的并发性,GNU Parallel会在文件读取结束后再分发参数。

GNU Parallel 在读取完最后一个参数之后,才开始第二个任务,此时会把所有的参数平均分配到4个任务(如果指定了4个任务)。

第一个任务与上面使用 --xargs 的例子一样,但是第二个任务会被平均的分成4个任务,最终一共5个任务。

cat num30000 | parallel --jobs 4 -m echo | wc -l

输出:

5

10分参数分配到4个任务可以看得更清晰:

parallel --jobs 4 -m echo ::: {1..10}

输出:

1 2 3
4 5 6 
7 8 9
10

替换字符串可以是单词的一部分。通过下面两个命令体会 -m 和 -X 的区别:

parallel --jobs 4 -m echo pre-{}-post ::: A B C D E F G

输出:

pre-A B-post
pre-C D-post
pre-E F-post
pre-G-post

-X与 -m 相反:

parallel --jobs 4 -X echo pre-{}-post ::: A B C D E F G

输出:

pre-A-post pre-B-post
pre-C-post pre-D-post
pre-E-post pre-F-post
pre-G-post

使用 -N 限制每行参数的个数:

parallel -N3 echo ::: A B C D E F G H

输出:

A B C
D E F
G H

-N也可以用于指定位置替换字符串:


parallel -N3 echo 1={1} 2={2} 3={3} ::: A B C D E F G H

输出:

1=A 2=B 3=C
1=D 2=E 3=F
1=G 2=H 3=

-N0 只读取一个参数,但不附加:

parallel -N0 echo foo ::: 1 2 3

输出:

foo
foo
foo

引用

如果命令行中包含特殊字符,就需要使用引号保护起来。

perl脚本 ‘print “@ARGV\n”’ 与linux的 echo 的功能一样。

perl -e 'print "@ARGV\n"' A

输出:

A

使用GNU Parallel运行这条命令的时候,perl命令需要用引号包起来:

parallel perl -e 'print "@ARGV\n"' ::: This wont work

输出:

[Nothing]

使用 -q 保护perl命令:

parallel -q perl -e 'print "@ARGV\n"' ::: This works

输出:

This
works

也可以使用 ’ :

parallel perl -e \''print "@ARGV\n"'\' ::: This works, too

输出:

This
works,
too

使用 -quote:

parallel --shellquote
parallel: Warning: Input is read from the terminal. Only experts do this on purpose. Press CTRL-D to exit.
perl -e 'print "@ARGV\n"'
[CTRL-D]

输出:

perl\ -e\ \'print\ \"@ARGV\\n\"\'

也可以使用命令:

parallel perl\ -e\ \'print\ \"@ARGV\\n\"\' ::: This also works

输出:

This
also
works

去除空格

使用 --trim 去除参数两头的空格:

parallel --trim r echo pre-{}-post ::: ' A '

输出:

pre- A-post

删除左边的空格:

parallel --trim l echo pre-{}-post ::: ' A '

输出:

pre-A -post

删除两边的空格:

parallel --trim lr echo pre-{}-post ::: ' A '

输出:

pre-A-post

控制输出

以参数做为输出前缀:

parallel --tag echo foo-{} ::: A B C

输出:

A       foo-A
B       foo-B
C       foo-C

修改输出前缀 --tagstring:

parallel --tagstring {}-bar echo foo-{} ::: A B C

输出:

A-bar       foo-A
B-bar       foo-B
C-bar       foo-C

查看有哪些命令会被执行:

parallel --dryrun echo {} ::: A B C

输出:

echo A
echo B
echo C

运行之前先打印命令 --verbose:

parallel --verbose echo {} ::: A B C

输出:

echo A
echo B
A
echo C
B
C

GNU Parallel 会延迟输出,直到命令执行完成:

parallel -j2 'printf "%s-start\n%s" {} {};sleep {};printf "%s\n" -middle;echo {}-end' ::: 4 2 1

输出:

2-start
2-middle
2-end
1-start
1-middle
1-end
4-start
4-middle
4-end

立即打印输出 --ungroup:

parallel -j2 --ungroup 'printf "%s-start\n%s" {} {};sleep {};printf "%s\n" -middle;echo {}-end' ::: 4 2 1

输出:

4-start
42-start
2-middle
2-end
1-start
1-middle
1-end
-middle
4-end

使用 --ungroup 会很快,但会导致输出错乱,一个任务的行输出可能会被另一个任务的输出截断。像上例所示,第二行输出混合了两个任务: ‘4-middle’ ‘2-start’

使用 --linebuffer避免这个问题(稍慢一点):

parallel -j2 --linebuffer 'printf "%s-start\n%s" {} {};sleep {};printf "%s\n" -middle;echo {}-end' ::: 4 2 1

输出:

4-start
2-start
2-middle
2-end
1-start
1-middle
1-end
4-middle
4-end

强制使输出与参数保持顺序 --keep-order/-k:

parallel -j2 -k 'printf "%s-start\n%s" {} {};sleep {};printf "%s\n" -middle;echo {}-end' ::: 4 2 1

输出:

4-start
4-middle
4-end
2-start
2-middle
2-end
1-start
1-middle
1-end

把输出保存到文件中

GNU Parallel可以把每一个任务的输出保存到文件中:

parallel --files ::: A B C

输出与下面类似:

/tmp/pAh6uWuQCg.par
/tmp/opjhZCzAX4.par
/tmp/W0AT_Rph2o.par

临时文件默认保存在 /tmp 中,可以使用 --tmpdir改变(或者修改 $TMPDIR):

parallel --tmpdir /var/tmp --files ::: A B C

输出:

/var/tmp/pAh6uWuQCg.par
/var/tmp/opjhZCzAX4.par
/var/tmp/W0AT_Rph2o.par

或者修改 $TMPDIR :

TMPDIR=/var/tmp parallel --files ::: A B C

输出同上。

输出文件可以有结构的保存 --results:

parallel --results outdir echo ::: A B C

输出:

A
B
C

输出文件不仅包含标准输出(stdout)也会包含标准错误输出(stderr):

outdir/1/A/stderr
outdir/1/A/stdout
outdir/1/B/stderr
outdir/1/B/stdout
outdir/1/C/stderr
outdir/1/C/stdout

在使用多个变量的时候会显示很有用:

parallel --header : --results outdir echo ::: f1 A B ::: f2 C D

生成的文件:

outdir/f1/A/f2/C/stderr
outdir/f1/A/f2/C/stdout
outdir/f1/A/f2/D/stderr
outdir/f1/A/f2/D/stdout
outdir/f1/B/f2/C/stderr
outdir/f1/B/f2/C/stdout
outdir/f1/B/f2/D/stderr
outdir/f1/B/f2/D/stdout

控制执行

并行任务数

使用 --jobs/-j 指定并行任务数:

/usr/bin/time parallel -N0 -j64 sleep 1 ::: {1..128}

使用64个任务执行128个休眠命令,大概耗时2到8秒。

默认情况下并行任务数与cpu核心数相同,所以这条命令:

/usr/bin/time parallel -N0 sleep 1 ::: {1..128}

会比每个cpu两个任务的耗时多一倍:

/usr/bin/time parallel -N0 --jobs 200% sleep 1 ::: {1..128}

使用 --jobs 0 表示执行尽可能多的并行任务:

/usr/bin/time parallel -N0 --jobs 0 sleep 1 ::: {1..128}

通常耗时1到7秒。

可以从文件中读取并行任务数,这样的话,每个任务完成的时候都会重新读取一次文件:

echo 50% > my_jobs
/usr/bin/time parallel -N0 --jobs my_jobs sleep 1 ::: {1..128} &
sleep 1
echo 0 > my_jobs
wait

前两个任务都是只用了一半的cpu,当文件内容变成0之后,后面的任务就会尽可能多的并行执行。

除了基于cpu使用率之外,也可以基于cpu数:

parallel --use-cpus-instead-of-cores -N0 sleep 1 ::: {1..128}

交互

通过使用 --interactive 在一个任务执行之前让用户决定是否执行:

parallel --interactive echo ::: 1 2 3

输出:

echo 1 ?...y
echo 2 ?...n
1
echo 3 ?...y
3

总结

  1. 以上是对parallel安装及使用的整理,记录。
  2. 灵活使用parallel可以提升效率。
  3. 后续将介绍parallel用于CPU并行,充分挖掘CPU算力,用于分子对接软件实现高通量虚拟筛选。

参考资料:

https://www.cnblogs.com/achievegoals/p/9263506.html
https://www.yingsoo.com/news/posts/58584.html

Logo

更多推荐