Bash İÇİN Döngü Paralelleştirme


109

GNU Parallel'ı kullanarak ancak bunu başaramamış olmak üzere, aşağıdaki komut dosyasını, özellikle de üç FOR döngüsünün her birini paralel hale getirmeye çalışıyordum. FOR döngüsünde bulunan 4 komut seri olarak çalışır, her döngü yaklaşık 10 dakika sürer.

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

Yanıtlar:


94

Neden sadece onları çatallamıyorsunuz?

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

Net olmadığı takdirde, önemli kısım burada:

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

Fonksiyonun arka planda çatallı bir kabukta çalıştırılmasına neden olmak. Bu paralel.


6
Büyüleyici bir şekilde çalıştı. Teşekkür ederim. Böyle basit bir uygulama (Beni şimdi çok aptal hissettiriyor!).
Ravnoor S Gill

8
Paralel olarak çalışacak 8 dosyam olsaydı ancak sadece 4 çekirdekli, böyle bir ayarda birleştirilebilir mi ya da bir İş Zamanlayıcı gerektirebilir mi?
Ravnoor S Gill

6
Bu bağlamda gerçekten önemli değil; Sistemin çekirdeklerden daha etkin işlemlere sahip olması normaldir. Çok sayıda kısa göreviniz varsa , ideal olarak bir sayı veya çalışan iş parçacığı tarafından sunulan bir kuyruğu <çekirdek sayısıyla beslersiniz. Kabuk komut dosyasıyla gerçekten ne kadar sık ​​yapıldığını bilmiyorum (bu durumda, bunlar iş parçacığı olmaz, bağımsız işlemler olur), ancak göreceli olarak az sayıda uzun görevde bu anlamsız olur. İşletim sistemi zamanlayıcısı bunlarla ilgilenecektir.
goldilocks

17
Ayrıca wait, sonuna bir komut eklemek isteyebilirsiniz, böylece ana komut dosyası, arka plan işlerinin tümü yapılana kadar çıkmaz.
psusi 19:15

1
Aynı zamanda eşzamanlı işlem sayısını sınırlamakta fayda var: İşlemlerimin her biri bir çekirdeğin zamanının% 100'ünü yaklaşık 25 dakika kullanır. Bu, birçok kişinin iş yaptığı, 16 çekirdekli paylaşımlı bir sunucuda. Senaryonun 23 kopyasını çalıştırmam gerekiyor. Hepsini eşzamanlı olarak çalıştırırsam, sunucuyu elimden alır ve bir veya iki saat boyunca herkes için işe yaramaz hale getiririm (yük 30'a kadar çıkar, her şey yavaşlar). Sanırım bununla yapılabilirdi nice, ama sonra biter mi bilemiyorum ..
naught101

150

Örnek görev

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

Sıralı çalıştırmalar

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

Paralel çalışır

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

N-işlem kümelerinde paralel işlemler

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

FIFO'ları semafor olarak kullanmak ve yeni işlemlerin mümkün olan en kısa sürede üretilmesini ve aynı anda N işlemden daha fazla işlem yapılmamasını sağlamak için bunları kullanmak mümkündür. Ancak daha fazla kod gerektirir.

FIFO tabanlı bir semaforla N işlemleri:

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
İçindeki çizgi waittemelde tüm işlemlerin çalışmasına izin verir, nthişleme başlamadan önce diğerlerinin çalışmasını tamamlamalarını bekler, doğru mu?
naught101

Eğer isıfır olduğu, bekleyin diyoruz. iSıfır testinden sonra artış .
PSkocik

2
@ naught101 Evet. waitw / no arg tüm çocuklar için bekler. Bu biraz israf yapar. Boru tabanlı semafor yaklaşımı size daha akıcı eşzamanlılık sağlıyor (bunu bir süredir başarılı bir şekilde -nt/ -otkontrollerle birlikte özel bir kabuk tabanlı yapı sisteminde kullanıyorum )
PSkocik

1
@ BeowulfNode42 Çıkmanız gerekmez. Görevin geri dönüş durumu, görevin süreci sona erdikten / çöktükten sonra durum (veya o boyuta sahip bir şey) tekrar beşe yazıldığında semaforun tutarlılığına zarar vermez.
PSkocik

1
Bilginize, mkfifo pipe-$$komutun geçerli dizine uygun yazma erişimi gerekir. Bu nedenle /tmp/pipe-$$, mevcut dizinin ne olduğuna bağlı kalmak yerine, muhtemelen mevcut kullanıcı için yazma erişimine sahip olması gibi tam yolu belirtmeyi tercih ederim . Evet, tüm 3 oluşumunu da değiştirin pipe-$$.
BeowulfNode42

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

İşe yarayıp yaramadığı komutlarınıza bağlıdır; Onlara aşina değilim. rm *.matO paralel olarak çalışıyorsa çatışmalara maruz biraz görünüyor ...


2
Bu da mükemmel çalışıyor. Haklısın, bir süreç diğerine müdahale etmeden çalışmasını sağlamak rm *.matgibi bir şeyle değiştirmek zorunda kalacağım rm $run".mat". Teşekkür ederim .
Ravnoor S Gill

@RavnoorSGill Stack Exchange'e Hoş Geldiniz! Bu cevap sorununuzu çözdüyse, lütfen yanındaki onay işaretini tıklayarak kabul ettiğiniz şekilde işaretleyin.
Gilles

7
Unuttuğum için +1 wait.
goldilocks

5
Tonlarca “şey” varsa, bu tonlarca işleme başlamaz mı? Aynı anda sadece aklı başında bir dizi süreci başlatmak daha iyi olurdu, değil mi?
David Doria

1
Çok yardımcı ipucu! Bu durumda iplik sayısı nasıl ayarlanır?
Dadong Zhang

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

Bu, mevcut çekirdek sayısı kadar yinelemeyi paralel hale getiren semaforları kullanacaktır (-j +0, N + 0 işlerini paralel hale getireceğiniz anlamına gelir ; N, kullanılabilir çekirdek sayısıdır ).

sem - wait, for kodundaki tüm yinelemelerin art arda kod satırlarını çalıştırmadan önce yürütmeyi sonlandırmasını beklemesini söyler.

Not: GNU paralel projesinden "paralel" gerekecektir (sudo apt-get install paralel).


1
60'ı geçmek mümkün mü? mayın, yeterli dosya tanımlayıcısı olmadığını söyleyerek hata yapıyor.
Chovy

Bu da herhangi biri için diş telleri nedeniyle bir sözdizimi hatası veriyorsa , cevabına moritzschaefer tarafından bir göz atın .
Nicolai

10

Sık kullanmamın gerçekten kolay bir yolu:

cat "args" | xargs -P $NUM_PARALLEL command

Bu komut, "args" dosyasının her satırında, paralel olarak ve aynı anda en fazla $ NUM_PARALLEL içinden geçen komutu çalıştıracaktır.

Girdi argümanlarını farklı yerlerde kullanmanız gerekirse, xargs için -I seçeneğine de bakabilirsiniz.


6

Fsl işleri birbirine bağlı gibi görünüyor, bu yüzden 4 iş paralel olarak çalıştırılamaz. Bununla birlikte, pistler paralel olarak çalıştırılabilir.

Tek bir çalıştırmayı çalıştıran bir bash işlevi yapın ve bu işlevi paralel olarak çalıştırın:

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

Daha fazla bilgi edinmek intro videoları izleyin: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1 ve öğretici yürürken bir saat harcamak http://www.gnu.org/software/parallel/parallel_tutorial.html hakimiyetin çizgi bunun için seni sevecek.


Bir bash olmayan kabuk kullanıyorsanız, export SHELL=/bin/bashparalel koşmadan önce de yapmanız gerekir . Aksi takdirde, gibi bir hata alırsınız:Unknown command 'myfunc arg'
AndrewHarvey

1
@AndrewHarvey: Shebang bunun için değil mi?
naught101

5

Maksimum N işleminde eşzamanlı olarak paralel yürütme

#!/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'in cevabını gerçekten çok basit bir şekilde maksimum işlem sayısı üzerinde kontrol sağladığı için seviyorum. Ancak kılavuzda açıklandığı gibi , sem parantez ile çalışmaz.

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

İşi yapar.

-j + N CPU çekirdeği sayısına N ekleyin. Paralel olarak bu kadar iş için çalıştırın. Bilgi işlem yoğun işler için -j +0, aynı anda cpu-çekirdeği işlerini çalıştıracağından faydalıdır.

-j -N N sayısını CPU çekirdeği sayısından çıkarın. Paralel olarak bu kadar iş için çalıştırın. Değerlendirilen sayı 1'den az ise 1 kullanılacaktır. Ayrıca bakınız - use-cpus-yerine-çekirdeklerinin.


1

Benim durumumda semafor kullanamıyorum (Windows'da git-bash'tayım), bu yüzden başlamadan önce görevi N işçileri arasında paylaştırmak için genel bir yöntem buldum.

Görevler aynı miktarda zaman alırsa iyi çalışır. Dezavantajı, işçilerden birinin işini yapmak için uzun zaman alması durumunda, zaten bitenlerin yardım etmemesidir.

İşi N işçileri arasında bölmek (çekirdek başına 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'Nın çözümü ile sorun yaşadım . Sistemimde bir paket olarak GNU Parallel bulunmuyor semve manuel olarak oluşturup çalıştırdığımda bir istisna attı. Daha sonra FIFO semafor örneğini de denedim, bu da iletişim ile ilgili başka hatalar da getirdi.

@eyeApps xargs önerildi, ancak karmaşık kullanım durumumla nasıl çalışacağını bilmiyordum (örnekler memnuniyetle karşılanacaktır).

İşte, Nşu anda tarafından yapılandırıldığı şekilde işleri gerçekleştiren paralel işler için benim çözümüm _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
}

Örnek kullanım:

#!/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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.