Паралелізуйте цикл Bash FOR


109

Я намагався паралелізувати наступний скрипт, зокрема кожен із трьох екземплярів циклу FOR, використовуючи GNU Parallel, але не зміг. 4 команди, що містяться в циклі FOR, виконуються послідовно, кожен цикл займає близько 10 хвилин.

#!/bin/bash

kar='KAR5'
runList='run2 run3 run4'
mkdir normFunc
for run in $runList
do 
  fsl5.0-flirt -in $kar"deformed.nii.gz" -ref normtemp.nii.gz -omat $run".norm1.mat" -bins 256 -cost corratio -searchrx -90 90 -searchry -90 90 -searchrz -90 90 -dof 12 
  fsl5.0-flirt -in $run".poststats.nii.gz" -ref $kar"deformed.nii.gz" -omat $run".norm2.mat" -bins 256 -cost corratio -searchrx -90 90 -searchry -90 90 -searchrz -90 90 -dof 12 
  fsl5.0-convert_xfm -concat $run".norm1.mat" -omat $run".norm.mat" $run".norm2.mat"
  fsl5.0-flirt -in $run".poststats.nii.gz" -ref normtemp.nii.gz -out $PWD/normFunc/$run".norm.nii.gz" -applyxfm -init $run".norm.mat" -interp trilinear

  rm -f *.mat
done

Відповіді:


94

Чому ти просто не розщедриш їх (ака. Тло)?

foo () {
    local run=$1
    fsl5.0-flirt -in $kar"deformed.nii.gz" -ref normtemp.nii.gz -omat $run".norm1.mat" -bins 256 -cost corratio -searchrx -90 90 -searchry -90 90 -searchrz -90 90 -dof 12 
    fsl5.0-flirt -in $run".poststats.nii.gz" -ref $kar"deformed.nii.gz" -omat $run".norm2.mat" -bins 256 -cost corratio -searchrx -90 90 -searchry -90 90 -searchrz -90 90 -dof 12 
    fsl5.0-convert_xfm -concat $run".norm1.mat" -omat $run".norm.mat" $run".norm2.mat"
    fsl5.0-flirt -in $run".poststats.nii.gz" -ref normtemp.nii.gz -out $PWD/normFunc/$run".norm.nii.gz" -applyxfm -init $run".norm.mat" -interp trilinear
}

for run in $runList; do foo "$run" & done

Якщо це не зрозуміло, значна частина тут:

for run in $runList; do foo "$run" & done
                                   ^

Причиняючи функцію виконання у роздвоєній оболонці на задньому плані. Це паралельно.


6
Це спрацювало як шарм. Дякую. Така проста реалізація (Змушує мене зараз почуватися дурною!).
Ravnoor S Gill

8
У випадку, якщо у мене було 8 файлів для паралельного запуску, але лише 4 ядра, чи можна їх інтегрувати в таке налаштування або для цього потрібен планувальник роботи?
Ravnoor S Gill

6
Це насправді не має значення в цьому контексті; нормально, щоб система мала більш активні процеси, ніж ядра. Якщо у вас багато коротких завдань , в ідеалі ви будете годувати чергу, обслуговувану номером або робочими потоками <кількість ядер. Я не знаю, як часто це робиться на сценаріях оболонок (у такому випадку вони не будуть нитками, вони будуть незалежними процесами), але з відносно мало довгими завданнями це буде безглуздо. Про них подбає планувальник ОС.
золотинки

17
Ви також можете додати waitкоманду в кінці, щоб головний скрипт не вийшов, поки не виконуються всі фонові завдання.
psusi

1
Також я б корисно обмежити кількість одночасних процесів: кожен мій процес використовує 100% часу ядра протягом приблизно 25 хвилин. Це на спільному сервері з 16 ядрами, де багато людей працюють за роботою. Мені потрібно запустити 23 копії сценарію. Якщо я запускаю їх одночасно, то я болотую сервер і роблю його корисним для всіх інших протягом години-двох (навантаження йде до 30, все інше сповільнюється). Я думаю, що це можна було б зробити nice, але тоді я не знаю, чи колись це закінчиться ..
naught101

150

Зразок завдання

task(){
   sleep 0.5; echo "$1";
}

Послідовні прогони

for thing in a b c d e f g; do 
   task "$thing"
done

Паралельні біги

for thing in a b c d e f g; do 
  task "$thing" &
done

Паралельні запуски в N-процесових партіях

N=4
(
for thing in a b c d e f g; do 
   ((i=i%N)); ((i++==0)) && wait
   task "$thing" & 
done
)

Також можна використовувати FIFO в якості семафорів і використовувати їх для того, щоб нові процеси виникали якнайшвидше і що одночасно запускається не більше N процесів. Але для цього потрібно більше коду.

N процесів із семафором на основі FIFO:

open_sem(){
    mkfifo pipe-$$
    exec 3<>pipe-$$
    rm pipe-$$
    local i=$1
    for((;i>0;i--)); do
        printf %s 000 >&3
    done
}
run_with_lock(){
    local x
    read -u 3 -n 3 x && ((0==x)) || exit $x
    (
     ( "$@"; )
    printf '%.3d' $? >&3
    )&
}

N=4
open_sem $N
for thing in {a..g}; do
    run_with_lock task $thing
done 

4
Рядок з waitним в основному дозволяє виконувати всі процеси, поки він не потрапить у nthпроцес, а потім чекає, поки всі інші закінчать роботу, чи не так?
naught101

Якщо iнуль, дзвоніть, зачекайте. Збільшення iпісля нульового тесту.
PSkocik

2
@ naught101 Так. waitбез / арг не чекає на всіх дітей. Це робить його трохи марнотратним. Підхід на основі семафору надає вам більш вільну конкурентоспроможність (я використовую це в спеціальній системі побудови на оболонці разом із -nt/ -otуспішно перевіряє деякий час)
PSkocik

1
@ BeowulfNode42 Вам не доведеться виходити. Статус повернення задачі не зашкодить послідовності семафору, поки статус (або щось з такою довжиною байта) записується на фіфо після завершення / збоїв процесу завдання.
PSkocik

1
Команді FYI mkfifo pipe-$$потрібен відповідний доступ для запису до поточного каталогу. Тому я вважаю за краще вказати повний шлях, наприклад, /tmp/pipe-$$оскільки він, швидше за все, має доступ для запису для поточного користувача, а не покладатися на те, що є поточним каталогом. Так замінити всі 3 випадки pipe-$$.
BeowulfNode42

65
for stuff in things
do
( something
  with
  stuff ) &
done
wait # for all the something with stuff

Чи справді це працює, залежить від ваших команд; Я з ними не знайомий. Вигляд rm *.matтрохи схильний до конфліктів, якщо він працює паралельно ...


2
Це також ідеально. Ви маєте рацію, мені доведеться змінити rm *.matщось на кшталт, rm $run".mat"щоб змусити його працювати без того, щоб один процес заважав іншому. Дякую .
Ravnoor S Gill

@RavnoorSGill Ласкаво просимо до обміну стеками! Якщо ця відповідь вирішила вашу проблему, позначте її як прийняту , поставивши галочку біля неї.
Жиль

7
+1 за wait, про що я забув.
золотинки

5
Якщо є багато «речей», чи не це запустить багато процесів? Було б краще запустити лише розумну кількість процесів одночасно, правда?
Девід Дорія

1
Дуже корисна порада! Як налаштувати кількість потоків у цьому випадку?
Дадун Чжан

30
for stuff in things
do
sem -j+0 ( something
  with
  stuff )
done
sem --wait

Це використовуватиме семафори, паралелізуючи стільки ітерацій, скільки кількість наявних ядер (-j +0 означає, що ви будете паралелізувати N + 0 завданням , де N - кількість доступних ядер ).

sem --очекає, чекати, поки всі ітерації циклу for завершаться, перш ніж виконувати послідовні рядки коду.

Примітка: вам знадобиться "паралель" від паралельного проекту GNU (sudo apt-get install paralle).


1
чи можна пройти повз 60? моє видає помилку, кажучи про недостатню кількість дескрипторів файлів.
chovy

Якщо це кидає синтаксичну помилку через дужки для когось, подивіться на відповідь moritzschaefer.
Миколай

10

Один дуже простий спосіб, який я часто використовую:

cat "args" | xargs -P $NUM_PARALLEL command

Це запустить команду, паралельно проходячи у кожному рядку файла "args", паралельно, запустивши не більше $ NUM_PARALLEL одночасно.

Ви також можете розглянути параметр -I для xargs, якщо вам потрібно замінити вхідні аргументи в різних місцях.


6

Здається, завдання FSL залежать від іншого, тому 4 завдання не можна виконувати паралельно. Пробіги, однак, можна виконувати паралельно.

Зробіть функцію bash, що працює в одному циклі, і виконайте цю функцію паралельно:

#!/bin/bash

myfunc() {
    run=$1
    kar='KAR5'
    mkdir normFunc
    fsl5.0-flirt -in $kar"deformed.nii.gz" -ref normtemp.nii.gz -omat $run".norm1.mat" -bins 256 -cost corratio -searchrx -90 90 -searchry -90 90 -searchrz -90 90 -dof 12 
    fsl5.0-flirt -in $run".poststats.nii.gz" -ref $kar"deformed.nii.gz" -omat $run".norm2.mat" -bins 256 -cost corratio -searchrx -90 90 -searchry -90 90 -searchrz -90 90 -dof 12 
    fsl5.0-convert_xfm -concat $run".norm1.mat" -omat $run".norm.mat" $run".norm2.mat"
    fsl5.0-flirt -in $run".poststats.nii.gz" -ref normtemp.nii.gz -out $PWD/normFunc/$run".norm.nii.gz" -applyxfm -init $run".norm.mat" -interp trilinear
}

export -f myfunc
parallel myfunc ::: run2 run3 run4

Щоб дізнатися більше, перегляньте вступні відео: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1 та проведіть годину, прогулюючись підручником http://www.gnu.org/software/parallel/parallel_tutorial.html Ваша команда лінія полюбить вас за це.


Якщо ви використовуєте не-bash оболонку, вам знадобиться також export SHELL=/bin/bashперед паралельним запуском. Інакше ви отримаєте помилку на кшталт:Unknown command 'myfunc arg'
AndrewHarvey

1
@AndrewHarvey: чи не для чого це шебанг?
naught101

5

Паралельне виконання в максимальному паралельному процесі N

#!/bin/bash

N=4

for i in {a..z}; do
    (
        # .. do your stuff here
        echo "starting task $i.."
        sleep $(( (RANDOM % 3) + 1))
    ) &

    # allow only to execute $N jobs in parallel
    if [[ $(jobs -r -p | wc -l) -gt $N ]]; then
        # wait only for first job
        wait -n
    fi

done

# wait for pending jobs
wait

echo "all done"

3

Мені дуже подобається відповідь від @lev, оскільки вона забезпечує контроль над максимальною кількістю процесів дуже просто. Однак, як описано в посібнику , sem не працює з дужками.

for stuff in things
do
sem -j +0 "something; \
  with; \
  stuff"
done
sem --wait

Робить роботу.

-j + N Додайте N до числа ядер CPU. Виконайте до цього багато завдань паралельно. Для обчислювальних завдань -j +0 корисний, оскільки він буде одночасно виконувати завдання з кількістю ядер CPU.

-j -N Відняти N від кількості ядер CPU. Виконайте до цього багато завдань паралельно. Якщо оцінене число менше 1, то буде використано 1. Дивіться також - використання-cpus-замість-ядер.


1

У моєму випадку я не можу використовувати семафор (я в git-bash в Windows), тому я придумав загальний спосіб розділити завдання серед N працівників, перш ніж вони розпочнуться.

Це добре працює, якщо завдання займають приблизно стільки ж часу. Недоліком є ​​те, що якщо одному з робітників потрібен довгий час, щоб виконати свою частину роботи, інші, які вже закінчили, не допоможуть.

Розподіл роботи серед N працівників (1 на ядро)

# array of assets, assuming at least 1 item exists
listAssets=( {a..z} ) # example: a b c d .. z
# listAssets=( ~/"path with spaces/"*.txt ) # could be file paths

# replace with your task
task() { # $1 = idWorker, $2 = asset
  echo "Worker $1: Asset '$2' START!"
  # simulating a task that randomly takes 3-6 seconds
  sleep $(( ($RANDOM % 4) + 3 ))
  echo "    Worker $1: Asset '$2' OK!"
}

nVirtualCores=$(nproc --all)
nWorkers=$(( $nVirtualCores * 1 )) # I want 1 process per core

worker() { # $1 = idWorker
  echo "Worker $1 GO!"
  idAsset=0
  for asset in "${listAssets[@]}"; do
    # split assets among workers (using modulo); each worker will go through
    # the list and select the asset only if it belongs to that worker
    (( idAsset % nWorkers == $1 )) && task $1 "$asset"
    (( idAsset++ ))
  done
  echo "    Worker $1 ALL DONE!"
}

for (( idWorker=0; idWorker<nWorkers; idWorker++ )); do
  # start workers in parallel, use 1 process for each
  worker $idWorker &
done
wait # until all workers are done

0

У мене виникли проблеми з @PSkocikрішенням Росії. У моїй системі немає GNU Parallel як пакет, і він semкинув виняток, коли я створив і запустив його вручну. Потім я спробував приклад семафору FIFO, який також кинув деякі інші помилки щодо спілкування.

@eyeApps запропонував xargs, але я не знав, як змусити його працювати зі своїм складним випадком використання (приклади будуть вітатися).

Ось моє рішення для паралельних завдань, які обробляють до Nзавдань одночасно, як налаштовано _jobs_set_max_parallel:

_lib_jobs.sh:

function _jobs_get_count_e {
   jobs -r | wc -l | tr -d " "
}

function _jobs_set_max_parallel {
   g_jobs_max_jobs=$1
}

function _jobs_get_max_parallel_e {
   [[ $g_jobs_max_jobs ]] && {
      echo $g_jobs_max_jobs

      echo 0
   }

   echo 1
}

function _jobs_is_parallel_available_r() {
   (( $(_jobs_get_count_e) < $g_jobs_max_jobs )) &&
      return 0

   return 1
}

function _jobs_wait_parallel() {
   # Sleep between available jobs
   while true; do
      _jobs_is_parallel_available_r &&
         break

      sleep 0.1s
   done
}

function _jobs_wait() {
   wait
}

Приклад використання:

#!/bin/bash

source "_lib_jobs.sh"

_jobs_set_max_parallel 3

# Run 10 jobs in parallel with varying amounts of work
for a in {1..10}; do
   _jobs_wait_parallel

   # Sleep between 1-2 seconds to simulate busy work
   sleep_delay=$(echo "scale=1; $(shuf -i 10-20 -n 1)/10" | bc -l)

   ( ### ASYNC
   echo $a
   sleep ${sleep_delay}s
   ) &
done

# Visualize jobs
while true; do
   n_jobs=$(_jobs_get_count_e)

   [[ $n_jobs = 0 ]] &&
      break

   sleep 0.1s
done
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.