简单的 BASH 脚本变成多进程/“线程”BASH 脚本?

简单的 BASH 脚本变成多进程/“线程”BASH 脚本?

我有以下有效的 BASH 脚本,我在 Mac Pro 2010/Mojarve 操作系统上运行该脚本:

#!/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 内存、一次性全部损坏并且我发誓再也不会使用它的 Mac Mini 2018 上,大约需要3小时。

但这是 Mac Pro,这意味着虽然它很旧(2010 年),但它非常可靠,并且拥有 12 个 Xeon。这是相当低强度的工作,因此我通过将其设置为单一处理器而错过了额外的好处。我只是想让这个脚本与 10-15-30 个线程一起工作,希望这会加快速度,并且在一小时或更短的时间内完成;不是一天的大部分时间。

我的想法是:将 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

您可以直接运行它,但它将按顺序处理每个文件。相反,您想使用 GNUparallelxargs -P.例如,使用如下所示的包装器脚本,它将要处理的文件数除以您拥有的核心数。

请注意,根据具体情况rhubarb,这可能更多是 I/O 密集型任务,而不是 CPU 密集型任务,因此添加太多核心不会有帮助 - 事实上,它可能会减慢速度,因为磁盘 I/O 争用过多...特别是如果您在 HDD 而不是 SSD 上运行它。

您可能想要在下面的脚本中硬编码类似cores=4或 的内容,而不是像我那样使用(我这样写是因为我正在运行具有 16 个核心和 32 个线程的 threadripper 1950x......并且我不想并行运行 32 个作业,并作为如何从中提取有用信息的示例)。cores=8lscpu | awk ...lscpu

另建议:如果您有多个驱动器,请尝试进行安排,以便您从中读取 .wav 文件的目录位于一个驱动器上,而将 .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 作为文件名之间的分隔符,因此可以安全地用于任何文件名(使用换行符作为文件名分隔符并不安全,因为换行符是文件名中的有效字符)。

相关内容