GNUを使用した並列ラインベースの出力、一時ファイルは不要

GNUを使用した並列ラインベースの出力、一時ファイルは不要

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バイトずつ入力を読み込みます。)grepgrep --line-buffered '^'sed -u

出力が破棄されても、/dev/null私のシステムでは非並列システムより13倍遅くなりますlz4 -dmc /var/lib/apt/lists/*lz4(6.5秒対0.5秒)。

これはpasteCで書かれています。 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 

関連情報