
Tengo el siguiente script BASH funcional, que ejecuto en un sistema operativo 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;
Toma una lista de archivos WAV, revisa cada uno de ellos, escanea el archivo y luego produce un resultado y almacena los archivos TSV generados en otro lugar. El objetivo de 'rhubarb' es producir información de sincronización de labios a partir de una grabación (los archivos WAV). etc., etc., bla, bla.
El único problema con este script es que tarda entre 10 y 12 HORAS en ejecutar unos 3000 archivos wav. En mi Mac Mini 2018, más cutre y sin ECC, que una vez corrompió todo y prometí no volver a usarlo nunca más, me tomó alrededor de3horas.
Pero esta es una Mac Pro, lo que significa que, aunque es antigua (2010), es muy confiable y tiene 12x Xeons. Este es un trabajo de intensidad bastante baja, por lo que me estoy perdiendo ese jugo extra al convertirlo en un solo procesador. Sólo quiero que este script funcione con 10-15-30 subprocesos y espero que esto lo acelere y esté listo en una hora o menos; no la mayor parte del día.
Mis pensamientos son: dividir el directorio de WAV en grupos de (total_files/15), colocar estos listados en el archivo 1-15.txt, luego leer cada uno y procesarlo en 15 subprocesos separados. Pero eso es todo lo que he llegado :P
¿Alguien puede ayudar a hacer de este un script multiproceso? Soy un aficionado e hice este script con la ayuda de reddit.
Respuesta1
Con GNU Parallel puedes hacer algo como esto:
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
O (si realmente necesita la salida en un directorio diferente):
find /hummdinger/LoCI/LoCI_orig/VO/WAV_Processed/ -name "*.wav" |
parallel $rhubarb {} -o /hummdinger/LoCI/LoCI_orig/LOCI_GAME_FILES/Compiled/Windows/sync/{/.}.tsv
Respuesta2
Escriba su script para que repita sus argumentos. Por ejemplo:
#!/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
Guarde esto como, por ejemplo, myscript1.sh
y hágalo ejecutable con chmod +x myscript1.sh
.
Puede ejecutar esto directamente, pero procesará cada archivo de forma secuencial. En su lugar, desea ejecutarlo con GNU parallel
o xargs -P
. Por ejemplo, con un script contenedor como el siguiente, que divide la cantidad de archivos a procesar por la cantidad de núcleos que tiene.
Tenga en cuenta que, dependiendo exactamente de lo que rhubarb
haga, es probable que esto sea más una tarea vinculada a E/S que a la CPU, por lo que agregar demasiados núcleos no ayudará; de hecho, probablemente ralentizará las cosas, ya que habrá Habrá demasiada contención para la E/S del disco... especialmente si está ejecutando esto en un HDD en lugar de un SSD.
Es posible que desees codificar algo como cores=4
o cores=8
en el siguiente script en lugar de usarlo lscpu | awk ...
como lo hice yo (lo escribí así porque estoy ejecutando un threadripper 1950x con 16 núcleos y 32 threads... y no quería para ejecutar 32 trabajos en paralelo y también como un ejemplo de cómo se puede extraer información útil de lscpu
).
También se recomienda: si tiene más de una unidad, intente organizar las cosas de modo que el directorio desde el que lea los archivos .wav esté en una unidad y el directorio en el que escriba los archivos .tsv esté en otra. Esto eliminará la contención de E/S entre la lectura y la escritura de archivos. Si los archivos .tsv no son grandes, escríbalos en un directorio temporal en un disco ram tmpfs y muévalos a su ubicación final al final del script.
#!/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
guárdelo como, por ejemplo, myscript2.sh
y hágalo ejecutable con chmod +x myscript2.sh
.
Este es el script que ejecuta desde la línea de comando o cron, o lo que sea. A su vez, utiliza xargs
para ejecutar múltiples instancias myscript1.sh
en paralelo.
Ejecútelo como:
./myscript2.sh /hummdinger/LoCI/LoCI_orig/VO/WAV_Processed/
Por cierto, esto usa NUL como separador entre nombres de archivos, por lo que es seguro usarlo con cualquier nombre de archivo (usar una nueva línea como separador de nombres de archivo no es seguro porque la nueva línea es un carácter válido dentro de un nombre de archivo).