Loop shell paralel

11

Saya ingin memproses banyak file dan karena saya sudah ada di sini sekelompok core saya ingin melakukannya secara paralel:

for i in *.myfiles; do do_something $i `derived_params $i` other_params; done

Saya tahu solusi Makefile tetapi perintah saya membutuhkan argumen dari daftar globbing shell. Apa yang saya temukan adalah:

> function pwait() {
>     while [ $(jobs -p | wc -l) -ge $1 ]; do
>         sleep 1
>     done
> }
>

Untuk menggunakannya, yang harus dilakukan adalah meletakkan & setelah pekerjaan dan panggilan tunggu, parameter memberikan jumlah proses paralel:

> for i in *; do
>     do_something $i &
>     pwait 10
> done

Tetapi ini tidak bekerja dengan baik, misalnya saya mencobanya dengan misalnya untuk loop mengkonversi banyak file tetapi memberi saya kesalahan dan meninggalkan pekerjaan dibatalkan.

Saya tidak dapat percaya bahwa ini belum dilakukan karena diskusi di milis zsh sudah sangat tua sekarang. Jadi, apakah Anda tahu yang lebih baik?

matematika
sumber
Mirip dengan pertanyaan ini: superuser.com/questions/153630/... Lihat apakah teknik itu cocok untuk Anda.
JRobert
Akan sangat membantu jika Anda memposting pesan kesalahan.
Dijeda sampai pemberitahuan lebih lanjut.
@ Robert Robert ya saya tahu ini, tetapi ini tidak benar-benar membantu karena pendekatan makefile tidak akan berfungsi seperti yang saya katakan! @ Dennis: Ok, pertama saya membiarkan menjalankan top di samping menunjukkan kepada saya lebih dari jumlah proses yang ditentukan. Kedua itu tidak kembali ke prompt dengan benar. Ketiga yang saya katakan meninggalkan pekerjaan tidak benar: Saya hanya menempatkan indikator echo "DONE"setelah loop yang dijalankan sebelum pekerjaan aktif tidak selesai. => Ini membuat saya berpikir bahwa pekerjaan tidak dilakukan.
matematika

Jawaban:

15

Makefile adalah solusi yang bagus untuk masalah Anda. Anda bisa memprogram eksekusi paralel ini dalam sebuah shell, tetapi sulit, seperti yang Anda perhatikan. Implementasi paralel dari merek tidak hanya akan menangani pekerjaan awal dan mendeteksi pemutusannya, tetapi juga menangani penyeimbangan muatan, yang rumit.

Persyaratan untuk globbing bukanlah halangan: ada implementasi yang mendukungnya. GNU make, yang memiliki ekspansi wildcard seperti $(wildcard *.c)dan akses shell seperti $(shell mycommand)(mencari fungsi dalam manual pembuatan GNU untuk informasi lebih lanjut). Ini default makedi Linux, dan tersedia di sebagian besar sistem lain. Inilah kerangka Makefile yang bisa Anda sesuaikan dengan kebutuhan Anda:

sumber = $ (wildcard * .src)

semua: $ (sumber: .src = .tgt)

% .tgt: $ .src
    do_something $ <$$ (diturunkan_params $ <)> $ @

Jalankan sesuatu seperti make -j4mengeksekusi empat pekerjaan secara paralel, atau make -j -l3untuk menjaga rata-rata beban sekitar 3.

Gilles 'SANGAT berhenti menjadi jahat'
sumber
8

Saya tidak yakin seperti apa argumen turunan Anda. Tetapi dengan GNU Parallel http: // www.gnu.org/software/parallel/ Anda dapat melakukan ini untuk menjalankan satu pekerjaan per cpu core:

find . | parallel -j+0 'a={}; name=${a##*/}; upper=$(echo "$name" | tr "[:lower:]" "[:upper:]");
   echo "$name - $upper"'

Jika yang ingin Anda dapatkan hanyalah dengan mengubah .stension {.} Mungkin berguna:

parallel -j+0 lame {} -o {.}.mp3 ::: *.wav

Tonton video intro ke GNU Parallel di http://www.youtube.com/watch?v=OpaiGYxkSuQ

Ole Tange
sumber
7

Tidakkah menggunakan perintah shell waitberfungsi untuk Anda?

for i in *
do
    do_something $i &
done
wait

Loop Anda menjalankan suatu pekerjaan kemudian menunggu untuk itu, kemudian melakukan pekerjaan berikutnya. Jika hal di atas tidak bekerja untuk Anda, maka milik Anda mungkin bekerja lebih baik jika Anda pindah pwaitsetelahnya done.

Dijeda sampai pemberitahuan lebih lanjut.
sumber
tidak dengan 1 juta file, saya akan menjalankan 1 juta proses, atau apakah saya salah?
matematika
1
@brubelsabs: Ya, itu akan mencoba melakukan sejuta proses. Anda tidak mengatakan dalam pertanyaan Anda berapa banyak file yang perlu Anda proses. Saya pikir Anda perlu menggunakan forloop bersarang untuk membatasi itu: for file in *; do for i in {1..10}; do do_something "$i" & done; wait; done(belum diuji) Itu harus dilakukan sepuluh sekaligus dan tunggu sampai semua sepuluh dari masing-masing kelompok dilakukan sebelum memulai sepuluh berikutnya. Loop Anda melakukan satu per satu waktu membuat &moot. Lihat pertanyaan yang ditautkan oleh JRobert untuk opsi lain. Cari di Stack Overflow untuk pertanyaan lain yang mirip dengan pertanyaan Anda (dan yang itu).
Dijeda sampai pemberitahuan lebih lanjut.
Jika OP mengantisipasi jutaan file maka ia akan memiliki masalah dengan for i in *. Dia harus melewati argumen ke loop dengan pipa atau sesuatu. Kemudian alih-alih loop internal Anda bisa menjalankan penghitung tambahan dan menjalankan "micro-"wait"-s"setiap "$ ((i% 32))" -eq '0'
@ DennisWilliamson: menggabungkan waitdengan loop counter dalam bekerja dengan baik untuk saya. Terima kasih!
Joel Purra
3

Mengapa belum ada yang menyebutkan xargs?

Dengan asumsi Anda memiliki tiga argumen,

for i in *.myfiles; do echo -n $i `derived_params $i` other_params; done | xargs -n 3 -P $PROCS do_something

Kalau tidak, gunakan pembatas (null berguna untuk itu):

for i in *.myfiles; do echo -n $i `derived_params $i` other_params; echo -ne "\0"; done | xargs -0 -n 1 -P $PROCS do_something

EDIT: untuk yang di atas, setiap parameter harus dipisahkan oleh karakter nol, dan kemudian jumlah parameter harus ditentukan dengan xargs -n.

zebediah49
sumber
Ya dalam proyek kami seseorang telah memiliki ide yang sama, dan itu berfungsi dengan baik bahkan di bawah Windows dengan MSys.
matematika
0

Saya mencoba beberapa jawaban. Mereka membuat skrip sedikit lebih rumit dari yang dibutuhkan. Idealnya menggunakan parallelatau xargsakan lebih disukai tetapi jika operasi di dalam untuk loop rumit itu bisa bermasalah untuk membuat file garis besar dan panjang untuk memasok ke paralel. alih-alih kita bisa menggunakan sumber sebagai berikut

# Create a test file 
$ cat test.txt
task_test 1
task_test 2

# Create a shell source file 
$ cat task.sh
task_test()
{
    echo $1
}

# use the source under bash -c 
$ cat test.txt | xargs -n1 -I{} bash -c 'source task.sh; {}'
1
2

Jadi untuk solusi masalah Anda akan terlihat seperti

for i in *.myfiles; echo " do_something $i `derived_params $i` other_params
" >> commands.txt ; done

mendefinisikan melakukan sesuatu sebagai do_something.sh

do_something(){
process $1
echo $2 
whatever $3 

}

jalankan dengan xargataugnu parallel

   cat commands.txt | xargs -n1 -I{} -P8 bash -c 'source do_something.sh; {}'

Saya menganggap independensi fungsional dari iterasi untuk tersirat.

vegabondx
sumber