GNU并行的默认输出模式是--group
:
每个作业的输出都写入临时文件,并parallel
仅在作业完成后才传递到输出。
当对大于/tmp
空间的数据使用此默认输出模式时,
parallel lz4 -dc ::: /var/lib/apt/lists/*lz4 | wc
它会很慢并且会崩溃
parallel: Error: Output is incomplete.
Cannot append to buffer file in /tmp.
使用该模式时--ungroup
,行在中间分开,这会导致与
parallel --ungroup lz4 -dc ::: /var/lib/apt/lists/*lz4 | wc
unparallelized 的输出不同lz4 -dmc /var/lib/apt/lists/*lz4 | wc
。
根据parallel
手册页,这应该通过我理解的选项来解决
--line-buffer
:所有作业都有一个由并行读取的输出管道,如果任何作业的输出可用,它会逐行传递到并行的输出管道过程本身。 (编辑:我的意思是块中的行,就像将大量输入分散到并行进程一样,而不是每行一个系统调用,这会太慢。)
但这不起作用:
parallel --line-buffer lz4 -dc ::: /var/lib/apt/lists/*lz4 | wc -c
导致与--group
上面暗示的相同的磁盘已满错误。
parallel --line-buffer
没有临时文件如何使用?
系统是LUbuntu 20 LTS。parallel -V
返回20161222
。具有超线程(4 线程)的双核 i3-4130 上原始串行和并行解压性能的比较:
time ls -S /var/lib/apt/lists/*lz4 | parallel --ungroup lz4 -dc > /dev/null
1.461s
time lz4 -dmc /var/lib/apt/lists/*lz4 > /dev/null
3.069s
真实用例如下(不带 的解决方法--line-buffer
):
time lz4 -dmc /var/lib/apt/lists/*Contents* | grep -F $'/parallel\t' | sort -u
usr/bin/parallel universe/utils/moreutils,universe/utils/parallel
usr/bin/parallel universe/utils/parallel
usr/lib/R/library/parallel/R/parallel universe/math/r-base-core
usr/lib/cups/backend/parallel net/cups-filters
usr/share/doc-base/parallel universe/utils/parallel
real 0m5.349s
user 0m3.970s
sys 0m5.839s
time ls -S /var/lib/apt/lists/*Contents* | parallel lz4 -dc '{}' \| grep -F "\$'/parallel\t'" | sort -u
(same output as above)
real 0m3.669s
user 0m5.888s
sys 0m7.676s
这不仅可以并行化解压,还可以并行化后处理,并且是更好的解决方案,因为管道第一部分的工作不是 99%。
但是这种并行化整个管道的方法并不总是可行,因此对于第一步的输出不是很小并且需要流式传输的情况,一般问题仍然存在。
答案1
要执行您的建议,需要lz4
将每个管道的输出发送到单独的管道,并且需要一个从所有管道读取并将其输出拆分为几行的选择/轮询循环,或者让一个进程/线程处理每个管道。
这听起来像是一笔巨大的开销。即使没有这种开销,我发现在我的 12 年历史的 4 核 8 线程笔记本电脑上,带有快速 SSD printf '%s\0' /var/lib/apt/lists/*lz4 | xargs -r0 -n 1 -P8 lz4 -dc
(即使没有 GNU 并行开销)也不会比lz4 -dmc /var/lib/apt/lists/*lz4
.
理想情况下,您首先希望并行命令输出行缓冲。对于很多,你可以用 来做到这一点stdbuf -oL
。
但情况似乎并非如此lz4
,但您可以通过以下方式手动实现第二种方法(一个进程处理一个lz4
输出):
printf '%s\0' /var/lib/apt/lists/*lz4 |
stdbuf -oL xargs -r0 -n 1 -P4 sh -c 'lz4 -dc "$1" | paste' sh |
wc -c
(paste
这是一个一次处理一行输入的命令,并且可以确信对输出进行行缓冲stdbuf
,另请参见 GNUgrep
的grep --line-buffered '^'
;避免sed -u
一次输出一行,但一次读取一个字节的输入)。
即使将输出丢弃到/dev/null
,在我的系统上,速度也比非并行系统慢 13 倍lz4 -dmc /var/lib/apt/lists/*lz4
(6.5 秒 vs 0.5 秒)。
这是使用paste
用 C 编写的。GNU 并行是用 编写的perl
,如果内部确实支持类似的东西,那么很有可能它的效率会更低。
并行化(至少以这种方式)仅对产生相对较少文本输出的 CPU 密集型任务有意义,这lz4
与轻松解压缩相反。
答案2
20170822 版本的发行说明中写道:
- --line-buffer 不再使用临时文件。这速度更快,并且使得单个进程可以输出比可用磁盘空间更多的数据。
所以解决办法就是升级到20170822。
GNU Parallel 仍将临时文件用于其他用途,但不用于行缓冲。
如果您排长队,它也会做正确的事情:
#!/bin/bash
5gfile() {
# Create file with 5GB long line
perl -e '$a=(shift)x1000000;for(1..5000){print $a};print "\n"' $1 | lz4 > $1.lz4;
}
export -f 5gfile
parallel 5gfile ::: a b c d
echo The correct output: One line with a b c d
lz4 -dc {a..d}.lz4 | tr -s abcd
echo Output from parallel: One line with a b c d might be reordered
parallel --line-buffer lz4 -dc ::: {a..d}.lz4 |
tr -s abcd
echo Output from xargs with stdbuf -oL
echo This does not handle long lines because stdbuf -oL does not guarantee only full lines will be written
printf '%s\0' /tmp/*lz4 |
stdbuf -oL xargs -r0 -n 1 -P4 sh -c 'lz4 -dc "$1" | paste' sh |
tr -s abcd