並列を使用して一意の入力ファイルを一意の出力ファイルに処理する


18

入力ファイル(各ファイルに多数の入力行が含まれる)でいっぱいのディレクトリが指定されているシェルスクリプトの問題があり、それらを個別に処理し、各出力を一意のファイル(別名、file_1.inputのニーズ)にリダイレクトする必要がありますfile_1.outputなどにキャプチャされます)。

事前並列処理では、ディレクトリ内の各ファイルを反復処理してコマンドを実行しますが、プロセッサを圧倒しないように何らかのタイマー/カウント手法を実行します(各プロセスのランタイムが一定であると仮定)。ただし、常にそうなるとは限らないため、カスタムコードを記述せずにシェルスクリプトのマルチスレッド化を実現するには、「並列」のようなソリューションを使用するのが最善の方法と思われます。

これらの各ファイルを処理するために並行して実行する(およびコアを効率的に管理できるようにする)方法をいくつか考えましたが、それらはすべてハックのようです。私は非常に簡単なユースケースだと思うものを持っているので、可能な限りきれいに保つことを好むでしょう(そして並列の例では何も私の問題であるとは思えません)。

助けていただければ幸いです!

入力ディレクトリの例:

> ls -l input_files/
total 13355
location1.txt
location2.txt
location3.txt
location4.txt
location5.txt

スクリプト:

> cat proces_script.sh
#!/bin/sh

customScript -c 33 -I -file [inputFile] -a -v 55 > [outputFile]

更新:以下のOleの答えを読んだ後、自分の並列実装のために足りない部分をまとめることができました。彼の答えは素晴らしいですが、私が追加した研究とメモは次のとおりです。

完全なプロセスを実行する代わりに、概念実証コマンドから始めて、自分の環境で彼のソリューションを証明しようと考えました。私の2つの異なる実装(およびメモ)を参照してください。

find /home/me/input_files -type f -name *.txt | parallel cat /home/me/input_files/{} '>' /home/me/output_files/{.}.out

find(lsではなく、問題を引き起こす可能性があります)を使用して、入力ファイルディレクトリ内のすべての適用可能なファイルを検索し、その内容を別のディレクトリとファイルにリダイレクトします。上記の私の問題は読み取りとリダイレクトでした(実際のスクリプトは単純でした)ので、スクリプトをcatに置き換えることは概念の立証でした。

parallel cat '>' /home/me/output_files/{.}.out :::  /home/me/input_files/*

この2番目のソリューションは、パラレルの入力変数パラダイムを使用してファイルを読み取りますが、初心者にとっては、これははるかに混乱を招きます。私にとっては、find aとpipeを使用することで私のニーズは十分に満たされました。

回答:


27

GNU Parallelは、この種のタスク向けに設計されています。

parallel customScript -c 33 -I -file {} -a -v 55 '>' {.}.output ::: *.input

または:

ls | parallel customScript -c 33 -I -file {} -a -v 55 '>' {.}.output

CPUコアごとに1つのジョブを実行します。

GNU Parallelは次の方法で簡単にインストールできます。

wget https://git.savannah.gnu.org/cgit/parallel.git/plain/src/parallel
chmod 755 parallel
cp parallel sem

詳細については、GNU Parallelのイントロビデオをご覧くださいhttps : //www.youtube.com/playlist?list=PL284C9FF2488BC6D1


すばらしい回答(および並列使用の私の要求を読むための主要なポイント)。
J・ジョーンズ

5

これを行う標準的な方法は、キューをセットアップし、キューから何かを取り出して処理する方法を知っている任意の数のワーカーを生成することです。これらのプロセス間の通信にはfifo(別名名前付きパイプ)を使用できます。

以下は、概念を示すための単純な例です。

単純なキュースクリプト:

#!/bin/sh
mkfifo /tmp/location-queue
for i in inputfiles/*; do
  echo $i > /tmp/location-queue
done
rm /tmp/location-queue

そして労働者:

#!/bin/sh
while read file < /tmp/location-queue; do
  process_file "$file"
done

process_file ワーカーのどこかで定義でき、必要なことは何でもできます。

これらの2つの部分を取得したら、キュープロセスと任意の数のワーカープロセスを起動する簡単なモニターを作成できます。

監視スクリプト:

#!/bin/sh
queue.sh &
num_workers="$1"
i=0
while [ $i < $num_workers ]; do
  worker.sh &
  echo $! >> /tmp/worker.pids
  i=$((i+1))
done
monitor_workers

そこにあります。実際にこれを行う場合、モニターでfifoをセットアップし、キューとワーカーの両方にパスを渡すと、それらが連結されず、fifoの特定の場所に固定されない方が良いでしょう。答えで具体的にこのように設定したので、読んでいるときに何を使用しているかが明確になります。


モニターは、次のワーカーが終了するまで(別名、$ iが減少するまで)新しいワーカーのスポーンを一時停止するのに十分なほどスマートです。----私自身の編集に答えると、ワーカーは消えることはなく、すべての処理が使い果たされるまでファイルを処理します(したがって、「プロセッサー」内のwhileループも同様です)。
J・ジョーンズ

監視スクリプトの最後の「monitor_workers」行は何を実行していますか?
J・ジョーンズ

@JJones- monitor_workersまさにそうですprocess_file-それはあなたが望むものを何でもする関数です。モニターについて-あなたは正しかった。ワーカーのPIDを保存して(キル信号を送信できるようにします)、ワーカーを開始するときにカウンターをインクリメントする必要があります。回答を編集してそれを含めました。
ショーンJ.ゴフ

本当に感謝していますが、GNUを使用すべきだと思いますparallel。それは完全に実装されたあなたの考えだと思います。
モトボイ

5

もう一つの例:

ls *.txt | parallel 'sort {} > {.}.sorted.txt'

他の例は不必要に複雑であることがわかりました。ほとんどの場合、上記があなたが探していたものかもしれません。


4

並列化が可能な一般的に利用可能なツールはmakeです。GNU makeおよび他のいくつかには、-j並列ビルドを実行するオプションがあります。

.SUFFIXES: .input .output
.input.output:
        process_one_file <$< >$@.tmp
        mv -f $@.tmp $@

次のmakeように実行します(ファイル名に特殊文字が含まれていないと仮定しますが、これらの文字にmakeは適していません):

make -j 4 $(for x in *.input; do echo ${x%.*}.output; done)

私見これは最も賢い解決策です:)
h4unt3r

3

これは、現在のディレクトリにある大きなファイルセットに対して同じコマンドを実行することです。

#!/bin/sh
trap 'worker=`expr $worker - 1`' USR1  # free up a worker
worker=0  # current worker
num_workers=10  # maximum number of workers
for file in *.txt; do
    if [ $worker -lt $num_workers ]; then
        {   customScript -c 33 -I -file $file -a -v 55 > `basename $file .txt`.outtxt 
            kill -USR1 $$ 2>/dev/null  # signal parent that we're free
        } &
        echo $worker/$num_worker $! $file  # feedback to caller
        worker=`expr $worker + 1`
    else
        wait # for a worker to finish
    fi
done

これはcustomScripttxtファイルで実行され、出力をファイルに入れouttxtます。必要に応じて変更します。これを機能させるための鍵は、SIGUSR1を使用したシグナル処理です。これにより、子プロセスは親プロセスに完了を知らせることができます。SIGCHLDを使用しても、スクリプト内のステートメントのほとんどがシェルスクリプトにSIGCHLDシグナルを生成するため、機能しません。コマンドをsleep 1で置き換えてみました。プログラムは0.28秒のユーザーCPUと0.14秒のシステムCPUを使用しました。これは約400個のファイルのみでした。


現在待機中の同じファイルを取得して、兄弟の「if」ステートメントを再入力するのに十分な「待機」方法
Jジョーンズ

それはwait十分に「スマート」なものではありません。しかし、SIGUSR1信号を取得した後に戻ります。子/ワーカーはa SIGUSR1を親に送信します。親はキャッチされ(trap)、デクリメント$workertrap句)し、から異常に戻りwaitif [ $worker -lt $num_workers ]句の実行を許可します。
アルセージュ

0

またはxargs -P、追加のソフトウェアをインストールする必要はなく、単に使用します:

find . -type f -print0 | xargs -0 -I'XXX' -P4 -n1 custom_script -input "XXX" -output "XXX.out"

オプションの説明:

  • -I'XXX' コマンドテンプレートでファイル名に置き換えられる文字列を設定します
  • -P4 4つのプロセスを並行して実行します
  • -n1 2つのXXXが見つかっても、実行ごとに1つのファイルのみを配置します
  • -print0そして、-0一緒に仕事、あなたはファイル名に(空白文字のような)特殊文字を持たせます
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.