シンプルな BASH スクリプトをマルチプロセス/「スレッド」 BASH スクリプトにできますか?

シンプルな BASH スクリプトをマルチプロセス/「スレッド」 BASH スクリプトにできますか?

私は、Mac Pro 2010/Mojarve OS で実行している次の動作する BASH スクリプトを持っています。

#!/bin/bash

c=0
cnt=0

# count up wav files
cnt=$(find /hummdinger/LoCI/LoCI_orig/VO/WAV_Processed/ -name "*.wav" | wc -l)
echo "there are $cnt .wav voice samples."

# go through and run rhubarb on them
for f in $(find /hummdinger/LoCI/LoCI_orig/VO/WAV_Processed/ -name "*.wav")
do
    c=$((c+1))
    echo "$c of $cnt";
    f=$(basename "$f" .wav)
    /hummdinger/LoCI/LoCI_orig/TSV/rhubarb-lip-sync-1.10.0-osx/rhubarb /hummdinger/LoCI/LoCI_orig/VO/WAV_Processed/"$f".wav -o /hummdinger/LoCI/LoCI_orig/LOCI_GAME_FILES/Compiled/Windows/sync/"$f".tsv
done;

これは WAV ファイルのリストを受け取り、各ファイルを調べてスキャンし、出力を生成して、生成された TSV ファイルを別の場所に保存します。「rhubarb」のポイントは、録音 (WAV ファイル) からリップシンク情報を生成することです。などなど。

このスクリプトの唯一の問題は、約3,000個のwavファイルを実行するのに約10~12時間かかることです。私の粗悪なECC非搭載のRAMでは、一度全部壊れてしまい二度と使わないと誓ったMac Mini 2018では、約10~12時間かかりました。3時間。

しかし、これは Mac Pro です。つまり、古い (2010 年製) ですが、非常に信頼性が高く、12 個の Xeon を搭載しています。これは非常に低負荷の作業なので、シングル プロセッサにすることで余分なパワーを逃しています。このスクリプトを 10 ~ 15 ~ 30 スレッドで動作させたいだけです。これで速度が上がり、1 時間かそれ以下で完了するでしょう。1 日の大半はかかりません。

私の考えは、WAV のディレクトリを (total_files/15) のグループに分割し、これらのリストを file1-15.txt に入れて、それぞれを読み戻して 15 個の個別のスレッドで処理することです。でも、私が思いついたのはここまでです :P

これをマルチプロセス スクリプトにするのを手伝ってくれる人はいませんか? 私はアマチュアですが、reddit の助けを借りてこのスクリプトを作成しました。

答え1

GNU Parallel を使用すると、次のようなことができます。

rhubarb=/hummdinger/LoCI/LoCI_orig/TSV/rhubarb-lip-sync-1.10.0-osx/rhubarb 

find /hummdinger/LoCI/LoCI_orig/VO/WAV_Processed/ -name "*.wav" |
  parallel $rhubarb {} -o {.}.tsv

または(本当に別のディレクトリに出力が必要な場合):

find /hummdinger/LoCI/LoCI_orig/VO/WAV_Processed/ -name "*.wav" |
  parallel $rhubarb {} -o /hummdinger/LoCI/LoCI_orig/LOCI_GAME_FILES/Compiled/Windows/sync/{/.}.tsv

答え2

引数を反復処理するようにスクリプトを記述します。例:

#!/bin/bash

rhubarb='/hummdinger/LoCI/LoCI_orig/TSV/rhubarb-lip-sync-1.10.0-osx/rhubarb'
outdir='/hummdinger/LoCI/LoCI_orig/LOCI_GAME_FILES/Compiled/Windows/sync/'

for fn in "$@"; do
    bn=$(basename "$fn" .wav)
    "$rhubarb" "$fn" -o "$outdir/$bn.tsv"
done

これを、例えば として保存しmyscript1.sh、 で実行可能にしますchmod +x myscript1.sh

parallelこれを直接実行することもできますが、各ファイルを順番に処理します。代わりに、GNUまたはで実行する必要がありますxargs -P。たとえば、次のようなラッパー スクリプトを使用して、処理するファイルの数をコアの数で割ります。

正確に何が行われるかにもよりますがrhubarb、これは CPU バウンドというよりは I/O バウンドのタスクになる可能性が高いため、コアを追加しすぎても役に立ちません。実際、ディスク I/O の競合が多すぎるため、速度が低下する可能性があります...特に、これを SSD ではなく HDD で実行している場合はそうです。

私が使用したように を使用するのではなく、以下のスクリプトでcores=4または のようなものをハードコードすることをお勧めします(16 コアと 32 スレッドの Threadripper 1950x を実行しているため、32 のジョブを並列に実行したくなかったため、そのように記述しました。また、 から有用な情報を抽出する方法の例でもあります)。cores=8lscpu | awk ...lscpu

また、次のこともお勧めします: ドライブが複数ある場合は、.wav ファイルの読み取り元ディレクトリが 1 つのドライブにあり、.tsv ファイルの書き込み先ディレクトリが別のドライブにあるように配置してください。これにより、ファイルの読み取りと書き込みの間の I/O 競合がなくなります。.tsv ファイルのサイズがそれほど大きくない場合は、tmpfs ramdisk 上の一時ディレクトリに書き込み、スクリプトの最後に最終的な場所に移動します。

#!/bin/bash

wavdir="$1"

cores=$(lscpu | awk -F': +' '/^CPU\(s\):/ {cpus=$2};
                             /^Thread\(s\) per core:/ {tpc=$2};
                             END { print int(cpus / tpc) }')

count=$(find "$wavdir" -type f -name "*.wav" -print0 |
          perl -0ne '$c++;END{print $c}')

let files_per_thread=count/cores

find "$wavdir" -type f -name "*.wav" -print0 |
    xargs -0 -r -L "$files_per_thread" -P "$cores" /path/to/myscript1.sh

これを、例えば として保存しmyscript2.sh、 で実行可能にしますchmod +x myscript2.sh

これは、コマンド ラインや cron などから実行するスクリプトです。これにより、xargs複数のインスタンスをmyscript1.sh並行して実行します。

次のように実行します:

./myscript2.sh /hummdinger/LoCI/LoCI_orig/VO/WAV_Processed/

ちなみに、これはファイル名間の区切り文字として NUL を使用するため、どのファイル名でも安全に使用できます (改行はファイル名内で有効な文字であるため、ファイル名の区切り文字として改行を使用することは安全ではありません)。

関連情報