从 Use Multiple CPU Cores(Parallelize) with Single Threaded Linux Commands 看到的,记录一下。
使用parallel加速单线程程序的原理在于使用 --pipe/--spreadstdin
将标准输入的内容切成多段,然后每段调用一个进程来处理,最后将处理结果整合在一起。比如:
ls -lh big.txt
-rw-r--r-- 1 westlund staff 102M Nov 1 21:23 big.txt
time grep regex bigfile.txt
real 0m2.376s
user 0m2.354s
sys 0m0.021s
time cat big.txt | parallel --pipe grep regex
real 0m2.592s
user 0m5.908s
sys 0m2.498s
但是你会发现上面用 parallel
进行并行处理后所花费的时间反而更多了,这是因为 --pipe
默认将stdin的内容分成每块1M,而且默认的并发量是CPU的个数。
也就是说上面 102M
的文件内容会分成 102
个块,然后调用 102
个 grep
进程来处理,最后将结果进行合并,这个过程显然是太繁杂了。
为此我们可以使用 --block
参数来指定每块的大小,比如
time cat big.txt | parallel --block 25M --pipe grep regex
real 0m1.626s
user 0m4.634s
sys 0m0.620s
这就明显快了很多了。
将STDIN拆分成多块后再并发处理在提高效率的同时也会带来一些问题:
并发运行多个进程时,就无法保证输出结果的一致性了, 比如下面这个例子:
echo -n 2 1 4 3 | parallel -d " " -j4 "sleep {}; echo {}" # -d的意思是设定分隔符,-j的意思是指定并发数
1
2
3
4
你会发现输出的顺序发生了改变。但这个问题可以通过 -k/--keep-order
来解决,比如:
echo -n 2 1 4 3 | parallel -d " " -k -j4 "sleep {}; echo {}" # -d的意思是设定分隔符,-j的意思是指定并发数
2
1
4
3
这次输出的顺序一样了
比如,我要计算一系列数字的总和,很明显这种任务不能单纯的用拆分来解决,拆分后还需要有一个合并的过程。比如
ls -lh random_numbers.txt
-rw-r--r-- 1 westlund staff 22M Nov 1 21:56 random_numbers.txt
time cat random_numbers.txt | awk '{sum+=$1} END {print sum}'
65538384640
real 0m2.408s
user 0m2.402s
sys 0m0.021s
time cat random_numbers.txt | parallel --block 2M --pipe awk \'{sum+=\$1} END {print sum}\'
6067097462
6064980068
6074889658
6068292593
6073256962
6065663642
6068441658
6071296753
4846052534
6072985491
6065427819
real 0m1.436s
user 0m4.390s
sys 0m0.358s
time cat random_numbers.txt | parallel --block 2M --pipe awk \'{sum+=\$1} END {print sum}\' | awk '{sum+=$1} END {print sum}'
65538384640
real 0m1.408s
user 0m4.341s
sys 0m0.356s