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
。すべてのジョブには並列に読み取られる出力パイプがあり、どのジョブの出力も使用可能であれば、並列プロセスの出力パイプに1行ずつ渡されます。それ自体。 (編集:ラインごとに1つのシステムコールではなく、並列プロセスにわたって多くの入力を分散させるなど、ブロック単位の行を意味します。これは遅すぎます。)
しかし、これはうまくいきません。上記で示した
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
各パイプの出力を別々のパイプに送信する必要があり、すべてのパイプから読み取って出力を複数行に分割する選択/ポーリングループが必要です。パイプ。
これは膨大なコストのように聞こえます。このようなオーバーヘッドがなくても、高速SSDを搭載した12年の4コア8スレッドノートブック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
。
そうではありませんが、次のように2番目のアプローチ(1つの出力に対して1つのプロセス)を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
これは一度に1行ずつ入力を処理するコマンドで、出力を確実にラインバッファリングします。GNUstdbuf
も参照してください。一度に1行ずつ出力するのを避け、一度に1バイトずつ入力を読み込みます。)grep
grep --line-buffered '^'
sed -u
出力が破棄されても、/dev/null
私のシステムでは非並列システムより13倍遅くなりますlz4 -dmc /var/lib/apt/lists/*lz4
(6.5秒対0.5秒)。
これはpaste
Cで書かれています。 GNU並列処理は で書かれており、perl
内部的にこのような機能をサポートすれば、はるかに効率的ではない可能性が高くなります。
並列化(少なくともこの方法)は、lz4
簡単な解凍とは異なり、比較的少ないテキスト出力を生成するCPU集約タスクにのみ適しています。
答え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