Paralelkan Bash FOR Loop

109

Saya telah mencoba untuk memparalelkan skrip berikut, khususnya masing-masing dari tiga instance loop FOR, menggunakan GNU Parallel tetapi belum mampu. 4 perintah yang terkandung dalam loop FOR dijalankan secara seri, masing-masing loop membutuhkan waktu sekitar 10 menit.

#!/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
Ravnoor S Gill
sumber

Jawaban:

94

Mengapa Anda tidak bercabang saja (alias. Background) mereka?

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

Dalam hal itu tidak jelas, bagian yang penting ada di sini:

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

Menyebabkan fungsi yang akan dieksekusi di shell bercabang di latar belakang. Itu paralel.

goldilocks
sumber
6
Itu bekerja seperti pesona. Terima kasih. Implementasi yang begitu sederhana (Membuat saya merasa sangat bodoh sekarang!).
Ravnoor S Gill
8
Jika saya memiliki 8 file untuk dijalankan secara paralel tetapi hanya 4 core, dapatkah itu diintegrasikan dalam pengaturan seperti itu atau akankah itu membutuhkan Penjadwal Pekerjaan?
Ravnoor S Gill
6
Tidak masalah dalam konteks ini; itu normal bagi sistem untuk memiliki lebih banyak proses aktif daripada inti. Jika Anda memiliki banyak tugas pendek , idealnya Anda akan memberi makan antrian yang dilayani oleh nomor atau utas pekerja <jumlah inti. Saya tidak tahu seberapa sering itu benar-benar dilakukan dengan skrip shell (dalam hal ini, mereka tidak akan menjadi utas, mereka akan menjadi proses independen) tetapi dengan tugas yang relatif sedikit itu tidak ada gunanya. Penjadwal OS akan mengurusnya.
goldilocks
17
Anda juga mungkin ingin menambahkan waitperintah di akhir sehingga skrip master tidak keluar sampai semua pekerjaan latar belakang selesai.
psusi
1
Saya juga akan baik-baik saja untuk membatasi jumlah proses bersamaan: proses saya masing-masing menggunakan 100% waktu inti selama sekitar 25 menit. Ini ada di server bersama dengan 16 core, di mana banyak orang menjalankan pekerjaan. Saya perlu menjalankan 23 salinan skrip. Jika saya menjalankan semuanya secara bersamaan, maka saya membanjiri server, dan menjadikannya tidak berguna untuk semua orang selama satu atau dua jam (beban naik hingga 30, yang lainnya memperlambat jalannya). Saya kira itu bisa dilakukan dengan nice, tetapi kemudian saya tidak tahu apakah itu akan pernah selesai ..
non't101
150

Tugas sampel

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

Berjalan berurutan

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

Berjalan paralel

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

Paralel berjalan dalam batch proses-N

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

Juga dimungkinkan untuk menggunakan FIFO sebagai semafor dan menggunakannya untuk memastikan bahwa proses baru muncul sesegera mungkin dan bahwa tidak lebih dari proses N berjalan pada saat yang sama. Tetapi membutuhkan lebih banyak kode.

N memproses dengan semafor berbasis 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 
PSkocik
sumber
4
Baris waitdi dalamnya pada dasarnya memungkinkan semua proses berjalan, sampai menyentuh nthproses, lalu menunggu semua yang lain selesai berjalan, apakah itu benar?
naught101
Jika inol, panggilan tunggu. Peningkatan isetelah tes nol.
PSkocik
2
@ naught101 Ya. waittanpa arg menunggu semua anak. Itu membuatnya sedikit boros. Pendekatan semaphore berbasis-pipa memberi Anda konkurensi yang lebih lancar (Saya telah menggunakannya dalam sistem pembangunan berbasis cangkang khusus bersama dengan -nt/ -otberhasil memeriksa untuk sementara waktu sekarang)
PSkocik
1
@ BeowulfNode42 Anda tidak harus keluar. Status pengembalian tugas tidak akan merusak konsistensi semaphore selama status (atau sesuatu dengan itu dengan panjang gelombang) ditulis kembali ke fifo setelah proses tugas keluar / macet.
PSkocik
1
FYI mkfifo pipe-$$perintah perlu akses tulis yang sesuai ke direktori saat ini. Jadi saya lebih suka menentukan path lengkap seperti /tmp/pipe-$$itu karena kemungkinan besar memiliki akses tulis yang tersedia untuk pengguna saat ini daripada mengandalkan apa pun direktori saat ini. Ya ganti semua 3 kejadian dari pipe-$$.
BeowulfNode42
65
for stuff in things
do
( something
  with
  stuff ) &
done
wait # for all the something with stuff

Apakah itu benar-benar berfungsi tergantung pada perintah Anda; Saya tidak terbiasa dengan mereka. The rm *.matterlihat sedikit rawan konflik jika berjalan secara paralel ...

frostschutz
sumber
2
Ini berjalan dengan sempurna juga. Anda benar, saya harus mengubah rm *.matsesuatu seperti rm $run".mat"membuatnya bekerja tanpa satu proses mengganggu yang lain. Terima kasih .
Ravnoor S Gill
@RavnoorSGill Selamat Datang di Stack Exchange! Jika jawaban ini menyelesaikan masalah Anda, harap tandai sebagai diterima dengan mencentang tanda centang di sebelahnya.
Gilles
7
+1 untuk wait, yang saya lupa.
goldilocks
5
Jika ada banyak 'hal', bukankah ini akan memulai banyak proses? Akan lebih baik untuk memulai hanya sejumlah proses secara bersamaan, kan?
David Doria
1
Tip yang sangat membantu! Bagaimana cara mengatur jumlah utas dalam kasus ini?
Dadong Zhang
30
for stuff in things
do
sem -j+0 ( something
  with
  stuff )
done
sem --wait

Ini akan menggunakan semaphores, sejajar dengan iterasi sebanyak jumlah core yang tersedia (-j +0 berarti Anda akan memparalelkan pekerjaan N + 0 , di mana N adalah jumlah core yang tersedia ).

sem --wait memberitahu untuk menunggu sampai semua iterasi dalam for loop telah menghentikan eksekusi sebelum mengeksekusi baris kode yang berurutan.

Catatan: Anda membutuhkan "paralel" dari proyek paralel GNU (sudo apt-get install parallel).

lev
sumber
1
apakah mungkin melewati 60? Tambang saya membuat kesalahan mengatakan tidak cukup deskriptor file.
chovy
Jika ini melempar kesalahan sintaksis karena kawat gigi untuk siapa pun juga, lihat jawabannya oleh moritzschaefer.
Nicolai
10

Salah satu cara mudah yang sering saya gunakan:

cat "args" | xargs -P $NUM_PARALLEL command

Ini akan menjalankan perintah, melewati setiap baris file "args", secara paralel, berjalan paling banyak $ NUM_PARALLEL pada saat yang sama.

Anda juga dapat melihat opsi -I untuk xargs, jika Anda perlu mengganti argumen input di tempat yang berbeda.

eyeApps LLC
sumber
6

Tampaknya pekerjaan fsl tergantung pada satu sama lain, sehingga 4 pekerjaan tidak dapat dijalankan secara paralel. Akan tetapi, proses dapat dijalankan secara paralel.

Buat fungsi bash menjalankan menjalankan tunggal dan jalankan fungsi itu secara paralel:

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

Untuk mempelajari lebih lanjut tonton video intro: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1 dan menghabiskan satu jam berjalan melalui tutorial http://www.gnu.org/software/parallel/parallel_tutorial.html Perintah Anda baris akan mencintaimu karenanya.

Ole Tange
sumber
Jika Anda menggunakan shell non-bash, Anda juga harus melakukannya export SHELL=/bin/bashsebelum menjalankan paralel. Kalau tidak, Anda akan mendapatkan kesalahan seperti:Unknown command 'myfunc arg'
AndrewHarvey
1
@AndrewHarvey: bukankah itu gunanya shebang?
naught101
5

Eksekusi paralel dalam max N-proses bersamaan

#!/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"
Tomasz Hławiczka
sumber
3

Saya sangat menyukai jawaban dari @lev karena ia memberikan kontrol atas jumlah maksimum proses dengan cara yang sangat sederhana. Namun seperti yang dijelaskan dalam manual , sem tidak bekerja dengan tanda kurung.

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

Apakah pekerjaan itu.

-j + N Tambahkan N ke jumlah inti CPU. Jalankan hingga banyak pekerjaan ini secara paralel. Untuk menghitung pekerjaan intensif -j +0 berguna karena akan menjalankan sejumlah pekerjaan cpu-core secara bersamaan.

-j -N Kurangi N dari jumlah core CPU. Jalankan hingga banyak pekerjaan ini secara paralel. Jika angka yang dievaluasi kurang dari 1 maka 1 akan digunakan. Lihat juga --use-cpus-bukan-core.

moritzschaefer
sumber
1

Dalam kasus saya, saya tidak bisa menggunakan semaphore (saya di git-bash pada Windows), jadi saya menemukan cara umum untuk membagi tugas di antara para pekerja N, sebelum mereka mulai.

Ini bekerja dengan baik jika tugas-tugasnya memakan waktu yang kira-kira sama. Kerugiannya adalah, jika salah satu pekerja membutuhkan waktu lama untuk melakukan bagian pekerjaannya, yang lain yang sudah selesai tidak akan membantu.

Membagi pekerjaan di antara pekerja N (1 per inti)

# 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
geekley
sumber
0

Saya mengalami masalah dengan @PSkociksolusi. Sistem saya tidak memiliki GNU Parallel yang tersedia sebagai paket dan semmelemparkan pengecualian ketika saya membangun dan menjalankannya secara manual. Saya kemudian mencoba contoh semifor FIFO yang juga melemparkan beberapa kesalahan lain tentang komunikasi.

@eyeApps menyarankan xargs tetapi saya tidak tahu bagaimana cara membuatnya bekerja dengan use case saya yang kompleks (contoh akan diterima).

Berikut adalah solusi saya untuk pekerjaan paralel yang memproses hingga Npekerjaan sekaligus yang dikonfigurasi oleh _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
}

Contoh penggunaan:

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