Script BASH simples para se tornar um script BASH multiprocesso/'threaded'?

Script BASH simples para se tornar um script BASH multiprocesso/'threaded'?

Eu tenho o seguinte script BASH funcional, que executo em um sistema operacional 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;

Ele pega uma lista de arquivos WAV, examina cada um, verifica o arquivo e, em seguida, produz uma saída e armazena os arquivos TSV gerados em outro lugar. O objetivo do 'ruibarbo' é produzir informações de sincronização labial de uma gravação (os arquivos WAV). etc. etc., blá, blá.

O único problema com este script é que leva cerca de 10 a 12 HORAS para executar cerca de 3.000 arquivos wav. No meu RAM pior, não ECC, Mac Mini 2018, uma vez-corrompeu-todo-e-fiz-um-juro-de-nunca-usar-de-novo, demorou cerca de3horas.

Mas este é um Mac Pro, ou seja, embora seja antigo (2010), é muito confiável e possui 12x Xeons. Este é um trabalho de intensidade bastante baixa, então estou perdendo aquele suco extra ao torná-lo um processador único. Eu só quero que esse script funcione com 10-15-30 threads e espero que isso acelere e seja concluído em uma hora ou menos; não a maior parte do dia.

Meus pensamentos são: divida o diretório de WAVs em grupos de (total_files/15), coloque essas listagens em file1-15.txt, depois leia cada uma delas e processe-as em 15 threads separados. Mas isso foi tudo que consegui :P

Alguém pode ajudar a tornar este script de processo múltiplo? Sou amador e fiz esse script com ajuda do reddit.

Responder1

Com o GNU Parallel você pode fazer algo assim:

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

Ou (se você realmente precisar da saída em um diretório 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

Responder2

Escreva seu script para que ele repita seus argumentos. Por exemplo:

#!/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

Salve isso como, por exemplo, myscript1.she torne-o executável com chmod +x myscript1.sh.

Você pode executar isso diretamente, mas processará cada arquivo sequencialmente. Em vez disso, você deseja executá-lo com GNU parallelou xargs -P. Por exemplo, com um script wrapper como o seguinte, que divide o número de arquivos a serem processados ​​pelo número de núcleos que você possui.

Observe que, dependendo exatamente do que rhubarbacontece, é provável que seja mais uma tarefa vinculada à E/S do que à CPU, portanto, adicionar muitos núcleos não ajudará - na verdade, provavelmente tornará as coisas mais lentas, pois haverá haverá muita disputa para E/S de disco... especialmente se você estiver executando isso em um HDD em vez de um SSD.

Você pode querer codificar algo como cores=4ou cores=8no script abaixo, em vez de usar lscpu | awk ...como eu fiz (escrevi assim porque estou executando um threadripper 1950x com 16 núcleos e 32 threads... e não queria para executar 32 jobs em paralelo e também como um exemplo de como você pode extrair informações úteis de lscpu).

Também recomendado: se você tiver mais de uma unidade, tente organizar as coisas de forma que o diretório de onde você lê os arquivos .wav esteja em uma unidade e o diretório em que você grava os arquivos .tsv esteja em outra. Isso eliminará a contenção de E/S entre a leitura e a gravação dos arquivos. Se os arquivos .tsv não forem grandes, grave-os em um diretório temporário em um ramdisk tmpfs e mova-os para seu local final no final do 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

salve isso como, por exemplo, myscript2.she torne-o executável com chmod +x myscript2.sh.

Este é o script que você executa na linha de comando ou cron, ou qualquer outra coisa. Ele, por sua vez, usa xargspara executar várias instâncias myscript1.shem paralelo.

Execute como:

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

BTW, isso usa NUL como separador entre nomes de arquivos, portanto é seguro usar com qualquer nome de arquivo (usar nova linha como separador de nome de arquivo não é seguro porque nova linha é um caractere válido dentro de um nome de arquivo).

informação relacionada