stdinを並列プロセスに拡散する


13

stdin上のファイルのリストを処理するタスクがあります。プログラムの起動時間はかなり長く、各ファイルにかかる時間は大きく異なります。これらのプロセスを相当数生成し、ビジーでないプロセスに作業をディスパッチします。私が望んでいることをほとんど実行するいくつかの異なるコマンドラインツールがありますが、私はそれを2つのほぼ機能するオプションに絞り込みました:

find . -type f | split -n r/24 -u --filter="myjob"
find . -type f | parallel --pipe -u -l 1 myjob

問題はsplit、純粋なラウンドロビンを実行するため、プロセスの1つが遅れて残り、操作全体の完了が遅れることです。一方parallel、入力のN行またはバイトごとに1つのプロセスを生成したいので、起動時のオーバーヘッドに多くの時間を費やすことになります。

プロセスを再利用し、標準化されていない標準化されたプロセスにフィードラインを供給するこのようなものはありますか?


そのsplitコマンドはどこから来たのですか?名前が標準のテキスト処理ユーティリティと競合しています。
ジル「SO-悪であるのをやめる」

@Gilles、それはGNUのものです:"split(GNU coreutils)8.13"。xargsの奇妙な代替として使用することはおそらく意図された使用ではありませんが、私が見つけたいものに最も近いものです。
BCoates

2
私はそれについて考えてきましたが、根本的な問題はのインスタンスがmyjobより多くの入力を受け取る準備ができていることを知ることです。プログラムがより多くの入力を処理する準備ができていることを知る方法はありません。あなたが知ることができるのは、どこかのバッファ(パイプバッファ、stdioバッファ)がより多くの入力を受け取る準備ができていることです。プログラムの準備ができたら、何らかのリクエストを送信するように手配できますか(プロンプトを表示するなど)。
ジル「SO-悪であるのをやめる」

プログラムがstdinでbuferingを使用していないと仮定すると、read呼び出しに反応するFUSEファイルシステムがトリックを行います。それはかなり大きなプログラミングの努力です。
ジル「SO-悪であるのをやめる」

なぜあなた-l 1parallel引数で使用していますか?IIRCは、ジョブごとに1行の入力を処理するように指示します(つまり、myjobのフォークごとに1つのファイル名、大量の起動オーバーヘッド)。
cas

回答:


1

このような一般的なケースでは、それは不可能に見えます。これは、各プロセスにバッファがあり、外部からバッファを監視して次のエントリ(スケジューリング)を配置する場所を決定できることを意味します...もちろん、何かを書く(またはslurmのようなバッチシステムを使用する)

ただし、プロセスの内容によっては、入力を前処理できる場合があります。たとえば、ファイルをダウンロードしたり、DBからエントリを更新したりしたいが、それらの50%がスキップされることになった場合(したがって、入力に応じて大きな処理の違いがあります)、プリプロセッサをセットアップするだけですこれにより、どのエントリに時間がかかるか(ファイルが存在するか、データが変更されたかなど)が確認されるため、反対側から来るものはすべて、ほぼ同じ時間がかかることが保証されます。ヒューリスティックが完全ではない場合でも、かなり改善される可能性があります。他のファイルをファイルにダンプし、同じ方法で後で処理することもできます。

しかし、それはユースケースに依存します。


1

いいえ、一般的な解決策はありません。あなたのコーディネーターは、各プログラムが別の行を読む準備ができたときを知る必要があり、それを許可する標準はありません。できることは、STDOUTに行を追加して、それが消費されるのを待つことだけです。パイプライン上のプロデューサーが次のコンシューマーの準備ができているかどうかを判断する良い方法はありません。


0

そうは思いません。私のお気に入りの雑誌には、bashプログラミングに関する記事がありました。それを行うためのツールがあれば、彼らはそれらに言及していたと信じています。だから、あなたはの線に沿って何かが欲しい:

set -m # enable job control
max_processes=8
concurrent_processes=0

child_has_ended() { concurrent_processes=$((concurrent_processes - 1)) }

trap child_has_ended SIGCHLD # that's magic calling our bash function when a child processes ends

for i in $(find . -type f)
do
  # don't do anything while there are max_processes running
  while [ ${concurrent_processes} -ge ${max_processes}]; do sleep 0.5; done 
  # increase the counter
  concurrent_processes=$((concurrent_processes + 1))
  # start a child process to actually deal with one file
  /path/to/script/to/handle/one/file $i &
done

明らかに、実際の作業スクリプトへの呼び出しを好みに変更できます。私が言及した雑誌は、最初にパイプをセットアップし、実際にワーカースレッドを開始するようなことをします。確認しmkfifoてください。ただし、ワーカープロセスがマスタープロセスに、より多くのデータを受信する準備ができていることを知らせる必要があるため、このルートははるかに複雑です。したがって、データを送信するワーカープロセスごとに1つのfifoと、ワーカーからデータを受信するマスタープロセスに1つのfifoが必要です。

免責事項 私は頭の上部からそのスクリプトを書きました。構文に問題がある可能性があります。


1
これは要件を満たしていないようです。アイテムごとにプログラムの異なるインスタンスを起動しています。
ジル「SO-悪であるのをやめる」

通常find . -type f | while read iは、よりもを使用することをお勧めしますfor i in $(find . -type f)

0

GNU Parallelの場合、-blockを使用してブロックサイズを設定できます。ただし、実行中のプロセスごとに1ブロックのメモリを保持するのに十分なメモリが必要です。

これはあなたが探しているものではないことを理解していますが、現時点では許容できる回避策かもしれません。

タスクの平均時間が同じ場合、mbufferを使用できる可能性があります。

find . -type f | split -n r/24 -u --filter="mbuffer -m 2G | myjob"

0

これを試して:

mkfifo プロセスごとに。

次にtail -f | myjob、各fifoを掛けます。

たとえば、ワーカーのセットアップ(myjobプロセス)

mkdir /tmp/jobs
for X in 1 2 3 4
do
   mkfifo pipe$X
   tail -f pipe$X | myjob &
   jobs -l| awk '/pipe'$X'/ {print $2, "'pipe$X'"}' >> pipe-job-mapping
done

アプリケーション(myjob)によっては、ebs -sを使用して、停止したジョブを見つけることができます。それ以外の場合は、CPUでソートされたプロセスをリストし、リソースの消費が最も少ないプロセスを選択します。たとえば、さらに作業が必要なときにファイルシステムにフラグを設定するなどして、ジョブ自体を報告します。

入力を待っているときにジョブが停止すると仮定すると、

jobs -sl 停止したジョブのpidを見つけて、それを機能するように割り当てる

grep "^$STOPPED_PID" pipe-to-job-mapping | while read PID PIPE
do
   cat workset > $PIPE
done

私はこれをテストしました

garfield:~$ cd /tmp
garfield:/tmp$ mkfifo f1
garfield:/tmp$ mkfifo f2
garfield:/tmp$ tail -f f1 | sed 's/^/1 /' &
[1] 21056
garfield:/tmp$ tail -f f2 | sed 's/^/2 /' &
[2] 21058
garfield:/tmp$ echo hello > f1
1 hello
garfield:/tmp$ echo what > f2
2 what
garfield:/tmp$ echo yes > f1
1 yes

私が認めなければならないこれは、ymmvでした。


0

これを解決するために本当に必要なのは、何らかのタイプのキューメカニズムです。

SYSVメッセージキューなどのキューからジョブに入力を読み取らせてから、プログラムを並列で実行させて、単に値をキューにプッシュすることは可能ですか?

別の可能性は、次のように、キューにディレクトリを使用することです。

  1. find出力は、ディレクトリで処理する各ファイルへのシンボリックリンクを作成します。 pending
  2. 各ジョブプロセスはmv、ディレクトリ内にある最初のファイルからpending、という名前の兄弟ディレクトリまでを実行しますinprogress
  3. ジョブがファイルを正常に移動した場合、処理を実行します。それ以外の場合は、別のファイル名を見つけて移動しますpending

0

@ashの答えを詳しく説明すると、SYSVメッセージキューを使用して作業を分散できます。Cで独自のプログラムを作成したくない場合は、ipcmdそれを支援するというユーティリティがあります。ここに、出力find $DIRECTORY -type f$PARALLELいくつかのプロセスに渡すためにまとめたものを示します。

set -o errexit
set -o nounset

export IPCMD_MSQID=$(ipcmd msgget)

DIRECTORY=$1
PARALLEL=$2

# clean up message queue on exit
trap 'ipcrm -q $IPCMD_MSQID' EXIT

for i in $(seq $PARALLEL); do
   {
      while true
      do
          message=$(ipcmd msgrcv) || exit
          [ -f $message ] || break
          sleep $((RANDOM/3000))
      done
   } &
done

find "$DIRECTORY" -type f | xargs ipcmd msgsnd

for i in $(seq $PARALLEL); do
   ipcmd msgsnd "/dev/null/bar"
done
wait

以下がテスト実行です。

$ for i in $(seq 20 10 100) ; do time parallel.sh /usr/lib/ $i ; done
parallel.sh /usr/lib/ $i  0.30s user 0.67s system 0% cpu 1:57.23 total
parallel.sh /usr/lib/ $i  0.28s user 0.69s system 1% cpu 1:09.58 total
parallel.sh /usr/lib/ $i  0.19s user 0.80s system 1% cpu 1:05.29 total
parallel.sh /usr/lib/ $i  0.29s user 0.73s system 2% cpu 44.417 total
parallel.sh /usr/lib/ $i  0.25s user 0.80s system 2% cpu 37.353 total
parallel.sh /usr/lib/ $i  0.21s user 0.85s system 3% cpu 32.354 total
parallel.sh /usr/lib/ $i  0.30s user 0.82s system 3% cpu 28.542 total
parallel.sh /usr/lib/ $i  0.27s user 0.88s system 3% cpu 30.219 total
parallel.sh /usr/lib/ $i  0.34s user 0.84s system 4% cpu 26.535 total

0

あなたが特定の入力ファイルが処理されますどのくらいの時間を推定することができない限り、(彼らは通常の並列コンピューティングのシナリオでそうであるように-しばしばを通じてワーカープロセスは、スケジューラに報告するための方法を持っていないMPI)、あなたは運のうち、一般的に-一部のワーカーが他のワーカーよりも長い時間入力を処理するというペナルティーを支払う(入力の不平等のため)、またはすべての入力ファイルに対して単一の新しいプロセスを生成するというペナルティーを支払う。


0

GNU Parallelは過去7年間で変更されました。だから今日はそれができる:

この例は、4と5の読み取りが遅いため、プロセス4と5よりも多くのブロックがプロセス11と10に与えられることを示しています。

seq 1000000 |
  parallel -j8 --tag --roundrobin --pipe --block 1k 'pv -qL {}0000 | wc' ::: 11 4 5 6 9 8 7 10
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.