Linux | parallel 并行3
1.5 构建命令行
如果parallel之后没有给定命令,那么这些参数会被当做命令:
[21:29 sxuan@hulab ~]$ parallel ::: ls 'echo foo' pwd
a.txt
b.txt
c.txt
foo
/home/sxuan
此外,命令还可以是一个脚本文件,一个二进制可执行文件或一个bash的函数(须用 export -f 导出函数)
[21:42 sxuan@hulab ~]$ echo "echo \$*" > s.sh
[21:44 sxuan@hulab ~]$ parallel ./s.sh ::: "a b c f" "1 2 3 4"
a b c f
1 2 3 4
1.6 替换字符串
GNU Parallel支持多种替换字符串,默认使用 {},使用 -I 改变替换字符串符号 {}。其最常见的字符串替换包括以下几种:{.}
,去掉扩展名;{/}
,去掉路径,只保留文件名;{//}
,只保留路径;{/.}
,同时去掉路径和扩展名;{#}
,输出任务编号。同时对于每一个字符串替换都可以自己指定符号:-I
对应{}
;--extensionreplace
替换 {.}
;--basenamereplace
替换 {/}
;--dirnamereplace
替换{//}
;--basenameextensionreplace
替换 {/.}
;--seqreplace
替换 {#}
。
[22:02 sxuan@hulab ~]$ parallel echo ::: A/B.C ; parallel echo {} ::: A/B.C ; parallel -I ,, echo ,, ::: A/B.C
A/B.C
A/B.C
A/B.C
[22:04 sxuan@hulab ~]$ parallel echo {.} ::: A/B.C ; parallel --extensionreplace ,, echo ,, ::: A/B.C
A/B
A/B
[22:08 sxuan@hulab ~]$ parallel echo {/} ::: A/B.C ; parallel --basenamereplace ,, echo ,, ::: A/B.C
B.C
B.C
[22:08 sxuan@hulab ~]$ parallel echo {//} ::: A/B.C ; parallel --dirnamereplace ,, echo ,, ::: A/B.C
A
A
[22:10 sxuan@hulab ~]$ parallel echo {/.} ::: A/B.C ; parallel --basenameextensionreplace ,, echo ,, ::: A/B.C
B
B
[22:13 sxuan@hulab ~]$ parallel echo {#} ::: A B C ; parallel --seqreplace ,, echo ,, ::: A B C
1
2
3
1
2
3
同时,如果有多个输入源时,可以通过 {编号} 指定某一个输入源的参数:
[22:14 sxuan@hulab ~]$ parallel --xapply echo {1} and {2} ::: A B ::: C D
A and C
B and D
# 可以使用 / // /. 和 . 改变指定替换字符串
[22:14 sxuan@hulab ~]$ parallel echo /={1/} //={1//} /.={1/.} .={1.} ::: A/B.C D/E.F
/=B.C //=A /.=B .=A/B
/=E.F //=D /.=E .=D/E
# 位置可以是负数,表示倒着数
[22:16 sxuan@hulab ~]$ 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
1.7 按列输入和指定参数名
使用 --header
把每一行输入中的第一个值做为参数名。
[22:17 sxuan@hulab ~]$ parallel --xapply --header : echo f1={f1} f2={f2} ::: f1 A B ::: f2 C D | tee d.txt
f1=A f2=C
f1=B f2=D
使用 --colsep 把文件中的行切分为列,做为输入参数。
[22:31 sxuan@hulab ~]$ perl -e 'printf "f1\tf2\nA\tB\nC\tD\n"' > tsv-file.tsv
[22:32 sxuan@hulab ~]$ parallel --header : --colsep '\t' echo f1={f1} f2={f2} :::: tsv-file.tsv
f1=A f2=B
f1=C f2=D
1.8 多参数
--xargs
会在一行中输入尽可能多的参数(与参数字符串长度有关),通过-s
可指定一行中参数的上限。
[09:44 sxuan@hulab ~]$ perl -e 'for(1..30000){print "$_\n"}' > num30000
[09:50 sxuan@hulab ~]$ cat num30000 | parallel --xargs echo | wc -l
3
### 这里官网给出的例子是2行,我的计算机centos7上面的结果是3行,应该跟linux版本有关
[09:50 sxuan@hulab ~]$ cat num30000 | parallel --xargs -s 10000 echo | wc -l
17
为了获得更好的并发性,GNU Parallel会在文件读取结束后再分发参数。
GNU Parallel 在读取完最后一个参数之后,才开始第二个任务,此时会把所有的参数平均分配到4个任务(如果指定了4个任务)。
第一个任务与上面使用 --xargs
的例子一样,但是第二个任务会被平均的分成4个任务,最终一共5个任务。(奇怪的是我的结果与官网教程的结果不一样)
[11:44 sxuan@hulab ~]$ cat num30000 | parallel --jobs 4 -m echo | wc -l
6 ### 官网教程里这里是5
# 10分参数分配到4个任务可以看得更清晰 (这里的结果与官网一致)
[11:50 sxuan@hulab ~]$ parallel --jobs 4 -m echo ::: {1..10}
1 2 3
4 5 6
7 8 9
10
替换字符串可以是输出字符的一部分,使用-m
参数表示每个job不重复输出“背景”(context),-X
则与-m
相反,会重复输出“背景文本”,具体通过下面几个例子进行理解:
[11:36 sxuan@hulab ~]$ parallel --jobs 4 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
[11:51 sxuan@hulab ~]$ 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
[11:57 sxuan@hulab ~]$ 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
限制每行参数的个数,其中-N0
表示一次只读取一个参数,且不输入这个参数(作为计数器来使用)。
[12:04 sxuan@hulab ~]$ parallel -N4 echo 1={1} 2={2} 3={3} ::: A B C D E F G H
1=A 2=B 3=C
1=E 2=F 3=G
[12:05 sxuan@hulab ~]$ parallel -N0 echo foo ::: 1 2 3
foo
foo
foo
1.9 引用
如果命令行中包含特殊字符,就需要使用引号保护起来。
perl脚本 'print "@ARGV\n"' 与linux的 echo 的功能一样。
[12:05 sxuan@hulab ~]$ perl -e 'print "@ARGV\n"' A
A
使用GNU Parallel运行这条命令的时候,perl命令需要用引号包起来,也可以使用-q
保护perl命令:
[12:08 sxuan@hulab ~]$ parallel perl -e 'print "@ARGV\n"' ::: This wont work
[12:09 sxuan@hulab ~]$ parallel -q perl -e 'print "@ARGV\n"' ::: This works
This
works
[12:10 sxuan@hulab ~]$ parallel perl -e \''print "@ARGV\n"'\' ::: This works, too
This
works,
too
1.10 去除空格
使用--trim
去除参数两头的空格:
[12:10 sxuan@hulab ~]$ parallel --trim r echo pre-{}-post ::: ' A '
pre- A-post
[12:12 sxuan@hulab ~]$ parallel --trim l echo pre-{}-post ::: ' A '
pre-A -post
[12:12 sxuan@hulab ~]$ parallel --trim lr echo pre-{}-post ::: ' A '
pre-A-post
1.11 控制输出
使用--tag
以参数做为输出前缀,使用--tagstring
修改输出前缀:
[12:17 sxuan@hulab ~]$ parallel --tag echo foo-{} ::: A B C
A foo-A
B foo-B
C foo-C
[12:19 sxuan@hulab ~]$ parallel --tagstring {}-bar echo foo-{} ::: A B C
A-bar foo-A
B-bar foo-B
C-bar foo-C
--dryrun
作用类似于echo:
[12:19 sxuan@hulab ~]$ parallel --dryrun echo {} ::: A B C
echo A
echo B
echo C
[12:20 sxuan@hulab ~]$ parallel echo {} ::: A B C
A
B
C
--verbose
则在运行之前先打印命令:
[12:21 sxuan@hulab ~]$ parallel --verbose echo {} ::: A B C
echo A
echo B
echo C
A
B
C
一般来说,GNU Parallel 会延迟输出,直到一组命令执行完成。使用--ungroup
,可立刻打印输出已完成部分。
[13:45 sxuan@hulab ~]$ 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
[13:45 sxuan@hulab ~]$ 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
避免这个问题(稍慢一点):
4-start
2-start
2-middle
2-end
1-start
1-middle
1-end
4-middle
4-end
强制使输出与参数保持顺序 --keep-order/-k
:
[13:53 sxuan@hulab ~]$ 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
1.12 将输出保存到文件
GNU Parallel可以把每一个任务的输出保存到文件中,临时文件默认保存在 /tmp 中,可以使用 --tmpdir改变(或者修改 $TMPDIR):
[13:55 sxuan@hulab ~]$ parallel --files ::: A B C
/tmp/parfmNTJ.par
/tmp/parmioFz.par
/tmp/pargaTxf.par
[13:57 sxuan@hulab ~]$ parallel --tmpdir ~ --files ::: A B C
/home/sxuan/parLEXH7.par
/home/sxuan/parXsKsR.par
/home/sxuan/parZxytI.par
[13:58 sxuan@hulab ~]$ TMPDIR=~ parallel --files ::: A B C
/home/sxuan/par2tX6C.par
/home/sxuan/parorPJy.par
/home/sxuan/pari5TkI.par
输出文件可以有结构的保存 --results
,输出文件不仅包含标准输出(stdout)也会包含标准错误输出(stderr):
[13:59 sxuan@hulab ~]$ parallel --results outdir echo ::: A B C
A
B
C
[14:00 sxuan@hulab ~]$ tree outdir/
outdir/
└── 1
├── A
│ ├── seq
│ ├── stderr
│ └── stdout
├── B
│ ├── seq
│ ├── stderr
│ └── stdout
└── C
├── seq
├── stderr
└── stdout
4 directories, 9 files
在使用多个变量的时候会显示很有用:
# --header : will take the first value as name and use that in the directory structure.
[14:02 sxuan@hulab ~]$ parallel --header : --results outdir echo ::: f1 A B ::: f2 C D
A C
A D
B C
B D
[14:02 sxuan@hulab ~]$ tree outdir/
outdir/
└── f1
├── A
│ └── f2
│ ├── C
│ │ ├── seq
│ │ ├── stderr
│ │ └── stdout
│ └── D
│ ├── seq
│ ├── stderr
│ └── stdout
└── B
└── f2
├── C
│ ├── seq
│ ├── stderr
│ └── stdout
└── D
├── seq
├── stderr
└── stdout
9 directories, 12 files
1.13 控制执行
使用 --jobs/-j 指定并行任务数。
# 使用64个任务执行128个休眠命令
[15:02 sxuan@hulab ~]$ time parallel -N0 -j64 sleep 1 ::: {1..128}
real 0m2.759s
user 0m0.657s
sys 0m1.345s
# 默认情况下并行任务数与cpu核心数相同, 所以这条命令会比每个cpu两个任务的耗时多一倍
[15:03 sxuan@hulab ~]$ time parallel -N0 sleep 1 ::: {1..128}
real 0m3.478s
user 0m0.656s
sys 0m1.344s
# 每个cpu两个任务
[15:03 sxuan@hulab ~]$ time parallel -N0 --jobs 200% sleep 1 ::: {1..128}
real 0m2.659s
user 0m0.734s
sys 0m1.423s
# 使用 --jobs 0 表示执行尽可能多的并行任务
[15:03 sxuan@hulab ~]$ time parallel -N0 --jobs 0 sleep 1 ::: {1..128}
real 0m2.135s
user 0m0.651s
sys 0m1.477s
# 除了基于cpu使用率之外,也可以基于cpu数
[15:03 sxuan@hulab ~]$ time parallel --use-cpus-instead-of-cores -N0 sleep 1 ::: {1..128}
real 1m5.499s
user 0m0.950s
sys 0m1.897s
1.14 交互
通过使用 --interactive
在一个任务执行之前让用户决定是否执行。
[15:08 sxuan@hulab ~]$ parallel --interactive echo ::: 1 2 3
echo 1 ?...y
echo 2 ?...y
echo 3 ?...y
1
2
3
1.15 耗时
当job有大量的IO操作时,为避免“惊群效应”,可使用--delay
参数指定各个job开始的时间间隔。
[15:16 sxuan@hulab ~]$ parallel --delay 2.5 echo Starting {}\;date ::: 1 2 3
Starting 1
Tue Apr 17 15:21:41 CST 2018
Starting 2
Tue Apr 17 15:21:44 CST 2018
Starting 3
Tue Apr 17 15:21:46 CST 2018
若已知任务超过一定时间未反应则为失败则可以通过--timeout
指定等待时间避免无谓的等待。GNU parallel能计算所有任务运行时间的中位数,因此可以指定时间为中位数的倍数关系。
[15:35 sxuan@hulab ~]$ parallel --timeout 4.1 sleep {}\; echo {} ::: 2 4 6 8
2
4
[15:36 sxuan@hulab ~]$ parallel --timeout 200% sleep {}\; echo {} ::: 2.1 2.2 3 7 2.3
2.1
2.2
2.3
3
1.16 显示任务进度信息
GNU parallel有多种方式可用来动态的显示任务进度信息,如:
parallel --eta sleep ::: 1 3 2 2 1 3 3 2 1
parallel --progress sleep ::: 1 3 2 2 1 3 3 2 1
seq 1000 | parallel -j10 --bar '(echo -n {};sleep 0.1)' 2> >(zenity --progress --auto-kill --auto-close)
使用--joblog
参数能够生成各个任务的日志文件:
[15:39 sxuan@hulab ~]$ parallel --joblog /tmp/log exit ::: 1 2 3 0
[15:41 sxuan@hulab ~]$ cat /tmp/log
Seq Host Starttime JobRuntime Send Receive Exitval Signal Command
1 : 1523950890.344 0.018 0 0 1 0 exit 1
2 : 1523950890.350 0.014 0 0 2 0 exit 2
3 : 1523950890.357 0.006 0 0 3 0 exit 3
4 : 1523950890.363 0.006 0 0 0 0 exit 0
通过--resume-failed
参数可以重新运行失败的任务; --retry-failed
的作用与--resume-failed
类似,只是--resume-failed
从命令行读取失败任务,而--retry-failed
则是从日志文件中读取失败任务:
[15:41 sxuan@hulab ~]$ parallel --resume-failed --joblog /tmp/log exit ::: 1 2 3 0 0 0
[15:48 sxuan@hulab ~]$ cat /tmp/log
Seq Host Starttime JobRuntime Send Receive Exitval Signal Command
1 : 1523950890.344 0.018 0 0 1 0 exit 1
2 : 1523950890.350 0.014 0 0 2 0 exit 2
3 : 1523950890.357 0.006 0 0 3 0 exit 3
4 : 1523950890.363 0.006 0 0 0 0 exit 0
1 : 1523951289.575 0.029 0 0 1 0 exit 1
2 : 1523951289.580 0.025 0 0 2 0 exit 2
3 : 1523951289.585 0.019 0 0 3 0 exit 3
5 : 1523951289.591 0.013 0 0 0 0 exit 0
6 : 1523951289.604 0.004 0 0 0 0 exit 0
[15:48 sxuan@hulab ~]$ parallel --retry-failed --joblog /tmp/log
[15:50 sxuan@hulab ~]$ cat /tmp/log
Seq Host Starttime JobRuntime Send Receive Exitval Signal Command
1 : 1523950890.344 0.018 0 0 1 0 exit 1
2 : 1523950890.350 0.014 0 0 2 0 exit 2
3 : 1523950890.357 0.006 0 0 3 0 exit 3
4 : 1523950890.363 0.006 0 0 0 0 exit 0
1 : 1523951289.575 0.029 0 0 1 0 exit 1
2 : 1523951289.580 0.025 0 0 2 0 exit 2
3 : 1523951289.585 0.019 0 0 3 0 exit 3
5 : 1523951289.591 0.013 0 0 0 0 exit 0
6 : 1523951289.604 0.004 0 0 0 0 exit 0
1 : 1523951445.089 0.013 0 0 1 0 exit 1
2 : 1523951445.094 0.009 0 0 2 0 exit 2
3 : 1523951445.102 0.007 0 0 3 0 exit 3
1.17 终止任务
GNU parallel支持在某一情况下(如第一个失败或成功时,或者20%任务失败时)终止任务,终止任务又有两种类型,其一为立即终止(通过--halt now
指定),杀死所有正在运行的任务并停止生成新的任务,其二为稍后终止(通过--halt soon
指定),停止生成新任务并等待正在运行任务完成。
[15:50 sxuan@hulab ~]$ parallel -j2 --halt soon,fail=1 echo {}\; exit {} ::: 0 0 1 2 3
0
0
1
parallel: This job failed:
echo 1; exit 1
parallel: Starting no more jobs. Waiting for 1 jobs to finish.
2
parallel: This job failed:
echo 2; exit 2
[16:04 sxuan@hulab ~]$ parallel -j2 --halt now,fail=1 echo {}\; exit {} ::: 0 0 1 2 3
0
0
1
parallel: This job failed:
echo 1; exit 1
[16:05 sxuan@hulab ~]$ parallel -j2 --halt soon,fail=20% echo {}\; exit {} ::: 0 1 2 3 4 5 6 7 8 9
0
1
parallel: This job failed:
echo 1; exit 1
2
parallel: This job failed:
echo 2; exit 2
parallel: Starting no more jobs. Waiting for 1 jobs to finish.
3
parallel: This job failed:
echo 3; exit 3
[16:05 sxuan@hulab ~]$ parallel -j2 --halt now,success=1 echo {}\; exit {} ::: 1 2 3 0 4 5 6
1
2
3
0
parallel: This job succeeded:
echo 0; exit 0
GNU parallel还支持在任务失败后重试运行--retries
:
[16:06 sxuan@hulab ~]$ parallel -k --retries 3 'echo tried {} >>/tmp/runs; echo completed {}; exit {}' ::: 1 2 0
completed 1
completed 2
completed 0
[16:09 sxuan@hulab ~]$ cat /tmp/runs
tried 1
tried 2
tried 0
tried 1
tried 2
tried 1
tried 2
关于终止信号的高级用法参考官方入门文档。
1.18 资源限制
GNU parallel能够在开始一个新的任务前检查系统的负载情况防止过载(通过--load
可指定负载),同时还能检查系统是否使用了交换空间(swap)(通过--noswap
限制使用swap)。
[16:09 sxuan@hulab ~]$ parallel --load 100% echo load is less than {} job per cpu ::: 1
load is less than 1 job per cpu
[16:19 sxuan@hulab ~]$ parallel --noswap echo the system is not swapping ::: now
the system is not swapping now
同时,对于某些占用内存较多的程序,parallel会检查内存只有内存满足时才启动任务(通过--memfree
指定需要内存大小),而且在启动任务后内存不够50%时会杀掉最新开始的任务,直到这个任务完成再重新开始那些杀死的任务。
[16:24 sxuan@hulab ~]$ parallel --memfree 1G echo will run if more than 1 GB is ::: free
will run if more than 1 GB is free
还可以通过--nice
来指定任务的优先级。
[16:27 sxuan@hulab ~]$ parallel --nice 17 echo this is being run with nice -n ::: 17
this is being run with nice -n 17
1.19 远程操作
可使用-S host
来进行远程登陆:
parallel -S username@$SERVER1 echo running on ::: username@$SERVER1
1.20 文件传输
GNU parallel 文件传输使用的是rsync。
echo This is input_file > input_file
parallel -S $SERVER1 --transferfile {} cat ::: input_file
更多远程操作参见入门文档。
1.21 --pipe
--pipe
参数使得我们可以将输入(stdin)分为多块(block),然后分配给多个任务多个cpu以达到负载均衡,最后的结果顺序与原始顺序一致。使用--block
参数可以指定每块的大小,默认为1M。
[17:15 sxuan@hulab ~]$ perl -e 'for(1..1000000){print "$_\n"}' > num1000000
[17:16 sxuan@hulab ~]$ cat num1000000 | parallel --pipe wc
165668 165668 1048571
149796 149796 1048572
149796 149796 1048572
149796 149796 1048572
149796 149796 1048572
149796 149796 1048572
85352 85352 597465
如果不关心结果顺序,只想要快速的得到结果,可使用--round-robin
参数。没有这个参数时每块文件都会启动一个命令,使用这个参数后会将这些文件块分配给job数任务(通过--jobs
进行指定)。若想分配更为均匀还可同时指定--block
参数。
[17:17 sxuan@hulab ~]$ cat num1000000 | parallel --pipe -j4 --round-robin wc
299592 299592 2097144
315464 315464 2097143
149796 149796 1048572
235148 235148 1646037
[17:23 sxuan@hulab ~]$ cat num1000000 | parallel --pipe -j4 --block 2M --round-robin wc
299593 299593 2097151
315465 315465 2097150
299593 299593 2097151
85349 85349 597444
参考:
官方文档
GNU Parallel指南