我有以下有效的 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
。
您可以直接运行它,但它将按顺序处理每个文件。相反,您想使用 GNUparallel
或xargs -P
.例如,使用如下所示的包装器脚本,它将要处理的文件数除以您拥有的核心数。
请注意,根据具体情况rhubarb
,这可能更多是 I/O 密集型任务,而不是 CPU 密集型任务,因此添加太多核心不会有帮助 - 事实上,它可能会减慢速度,因为磁盘 I/O 争用过多...特别是如果您在 HDD 而不是 SSD 上运行它。
您可能想要在下面的脚本中硬编码类似cores=4
或 的内容,而不是像我那样使用(我这样写是因为我正在运行具有 16 个核心和 32 个线程的 threadripper 1950x......并且我不想并行运行 32 个作业,并作为如何从中提取有用信息的示例)。cores=8
lscpu | 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 作为文件名之间的分隔符,因此可以安全地用于任何文件名(使用换行符作为文件名分隔符并不安全,因为换行符是文件名中的有效字符)。