web-dev-qa-db-ja.com

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

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つのプロセスを生成したいと考えており、起動のオーバーヘッドに多くの時間を費やしてしまいます。

Stdinのブロックを解除したプロセスにプロセスとフィードラインを再利用するこのようなものはありますか?

13
BCoates

いいえ、一般的な解決策はありません。ディスパッチャは、各プログラムがいつ別の行を読み取る準備ができているかを知る必要がありますが、それを可能にする標準はありません。あなたができることは、STDOUTに行を置き、何かがそれを消費するのを待つことです。パイプライン上のプロデューサーが次のコンシューマーの準備ができているかどうかを判断するための良い方法は実際にはありません。

1
dannysauer

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

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

しかし、それはユースケースによって異なります。

1
estani

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

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

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

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

@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
kouk

特定の入力ファイルが処理される時間を見積もることができない限りおよびワーカープロセスには、スケジューラーにレポートを返す方法がありません(通常の並列コンピューティングシナリオで行う-多くの場合 [〜#〜] mpi [〜#〜] )、一般的に運が悪い-入力を処理する一部のワーカーのペナルティを他のワーカーよりも長く支払う入力の不平等の)、またはすべての入力ファイルに対して単一の新しいプロセスを生成するというペナルティを支払います。

0
peterph

これを試して:

プロセスごとに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)によっては、jobs-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
Johan

これを解決するために本当に必要なのは、ある種のキューメカニズムです。

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

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

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

GNUParallelは過去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
0
Ole Tange

私はそうは思いません。私のお気に入りの雑誌には、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が必要です。

[〜#〜]免責事項[〜#〜]私は頭のてっぺんからそのスクリプトを書きました。構文上の問題がある可能性があります。

0
Bananguin