menggunakan paralel untuk memproses file input unik ke file output unik

18

Saya memiliki masalah scripting shell di mana saya diberi direktori yang penuh dengan file input (setiap file yang mengandung banyak baris input), dan saya perlu memprosesnya secara individual, mengarahkan setiap output mereka ke file yang unik (alias, file_1.input perlu untuk ditangkap dalam file_1.output, dan sebagainya).

Pra-paralel , saya hanya akan mengulangi setiap file dalam direktori dan melakukan perintah saya, sambil melakukan semacam timer / teknik penghitungan untuk tidak membanjiri prosesor (dengan asumsi bahwa setiap proses memiliki runtime konstan). Namun, saya tahu itu tidak akan selalu menjadi masalah, jadi menggunakan solusi "paralel" sepertinya merupakan cara terbaik untuk mendapatkan skrip shell multi-threading tanpa menulis kode khusus.

Sementara saya telah memikirkan beberapa cara untuk menyiapkan paralel untuk memproses masing-masing file ini (dan memungkinkan saya untuk mengelola inti saya secara efisien), mereka semua tampak berantakan. Saya memiliki apa yang saya pikir adalah kasus penggunaan yang cukup mudah, jadi akan lebih memilih untuk menjaganya sebersih mungkin (dan tidak ada dalam contoh paralel yang tampaknya melompat keluar sebagai masalah saya.

Bantuan apa pun akan dihargai!

contoh direktori input:

> ls -l input_files/
total 13355
location1.txt
location2.txt
location3.txt
location4.txt
location5.txt

Naskah:

> cat proces_script.sh
#!/bin/sh

customScript -c 33 -I -file [inputFile] -a -v 55 > [outputFile]

Pembaruan : Setelah membaca jawaban Ole di bawah ini, saya dapat mengumpulkan potongan-potongan yang hilang untuk implementasi paralel saya sendiri. Meskipun jawabannya bagus, berikut adalah penelitian tambahan dan catatan yang saya ambil:

Alih-alih menjalankan proses penuh saya, saya pikir mulai dengan bukti perintah konsep untuk membuktikan solusinya di lingkungan saya. Lihat dua implementasi saya yang berbeda (dan catatan):

find /home/me/input_files -type f -name *.txt | parallel cat /home/me/input_files/{} '>' /home/me/output_files/{.}.out

Penggunaan menemukan (bukan ls, yang dapat menyebabkan masalah) untuk menemukan semua file yang berlaku dalam direktori file input saya, dan kemudian mengalihkan kontennya ke direktori dan file terpisah. Masalah saya di atas adalah membaca dan mengarahkan ulang (skrip yang sebenarnya sederhana), jadi mengganti skrip dengan kucing adalah bukti konsep yang bagus.

parallel cat '>' /home/me/output_files/{.}.out :::  /home/me/input_files/*

Solusi kedua ini menggunakan paradigma input variabel paralel untuk membaca file dalam, namun untuk pemula, ini jauh lebih membingungkan. Bagi saya, menggunakan find a dan pipa memenuhi kebutuhan saya dengan baik.

J Jones
sumber

Jawaban:

27

GNU Parallel dirancang untuk tugas-tugas semacam ini:

parallel customScript -c 33 -I -file {} -a -v 55 '>' {.}.output ::: *.input

atau:

ls | parallel customScript -c 33 -I -file {} -a -v 55 '>' {.}.output

Ini akan menjalankan satu pekerjaan per inti CPU.

Anda dapat menginstal GNU Parallel hanya dengan:

wget https://git.savannah.gnu.org/cgit/parallel.git/plain/src/parallel
chmod 755 parallel
cp parallel sem

Tonton video intro untuk GNU Parallel untuk mempelajari lebih lanjut: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

Ole Tange
sumber
Jawaban yang bagus (dan poin utama untuk membaca permintaan saya menggunakan paralel).
J Jones
5

Cara standar untuk melakukan ini adalah dengan mengatur antrian dan menelurkan sejumlah pekerja yang tahu cara menarik sesuatu dari antrian dan memprosesnya. Anda dapat menggunakan fifo (pipa bernama bernama) untuk komunikasi antara proses ini.

Di bawah ini adalah contoh naif untuk menunjukkan konsep tersebut.

Skrip antrian sederhana:

#!/bin/sh
mkfifo /tmp/location-queue
for i in inputfiles/*; do
  echo $i > /tmp/location-queue
done
rm /tmp/location-queue

Dan seorang pekerja:

#!/bin/sh
while read file < /tmp/location-queue; do
  process_file "$file"
done

process_file dapat didefinisikan di suatu tempat di pekerja Anda, dan dapat melakukan apa pun yang Anda butuhkan untuk dilakukan.

Setelah Anda memiliki dua potong itu, Anda dapat memiliki monitor sederhana yang memulai proses antrian dan sejumlah proses pekerja.

Skrip monitor:

#!/bin/sh
queue.sh &
num_workers="$1"
i=0
while [ $i < $num_workers ]; do
  worker.sh &
  echo $! >> /tmp/worker.pids
  i=$((i+1))
done
monitor_workers

Itu dia. Jika Anda benar-benar melakukan ini, lebih baik untuk mengatur fifo di monitor, dan meneruskan jalur ke antrian dan pekerja, sehingga mereka tidak digabungkan dan tidak terjebak ke lokasi tertentu untuk fifo. Saya mengaturnya dengan cara ini di jawaban khusus sehingga jelas apa yang Anda gunakan saat Anda membacanya.

Shawn J. Goff
sumber
Bagaimana monitor itu cukup pintar untuk menghentikan pemijahan pada pekerja baru sampai pekerja berikutnya selesai (alias, di mana $ saya pernah dikurangi)? ---- Menjawab edit saya sendiri, para pekerja tidak pernah pergi, mereka hanya memproses file sampai semua pemrosesan telah habis (maka loop sementara dalam 'prosesor' juga).
J Jones
Apa baris "monitor_workers" di akhir skrip monitor yang bekerja?
J Jones
@ Jon - monitor_workerssama seperti process_file- itu adalah fungsi yang melakukan apa pun yang Anda inginkan. Tentang monitor - Anda benar; itu harus menyimpan pids dari para pekerjanya (sehingga dapat mengirim sinyal mematikan) dan penghitung perlu bertambah ketika mulai pekerja. Saya sudah mengedit jawaban untuk memasukkan itu.
Shawn J. Goff
Saya sangat menghargai pekerjaan Anda, tetapi saya pikir Anda harus menggunakan GNU parallel. Saya pikir itu adalah ide Anda, sepenuhnya diimplementasikan.
motobói
5

Contoh lain:

ls *.txt | parallel 'sort {} > {.}.sorted.txt'

Saya menemukan contoh-contoh lain yang tidak perlu rumit, ketika dalam kebanyakan kasus di atas adalah apa yang mungkin Anda cari.

kaviar melambat
sumber
4

Alat yang tersedia secara umum yang dapat melakukan paralelisasi adalah make. GNU make dan beberapa lainnya memiliki -jopsi untuk melakukan build paralel.

.SUFFIXES: .input .output
.input.output:
        process_one_file <$< >[email protected]
        mv -f [email protected] $@

Jalankan makeseperti ini (saya berasumsi nama file Anda tidak mengandung karakter khusus, maketidak baik dengan itu):

make -j 4 $(for x in *.input; do echo ${x%.*}.output; done)
Gilles 'SANGAT berhenti menjadi jahat'
sumber
imho ini adalah solusi yang paling pintar :)
h4unt3r
3

Ini untuk melakukan perintah yang sama pada sejumlah besar file di direktori saat ini:

#!/bin/sh
trap 'worker=`expr $worker - 1`' USR1  # free up a worker
worker=0  # current worker
num_workers=10  # maximum number of workers
for file in *.txt; do
    if [ $worker -lt $num_workers ]; then
        {   customScript -c 33 -I -file $file -a -v 55 > `basename $file .txt`.outtxt 
            kill -USR1 $$ 2>/dev/null  # signal parent that we're free
        } &
        echo $worker/$num_worker $! $file  # feedback to caller
        worker=`expr $worker + 1`
    else
        wait # for a worker to finish
    fi
done

Ini menjalankan customScriptpada setiap txtfile, menempatkan output dalam outtxtfile. Ubah sesuai kebutuhan. Kunci untuk mendapatkan ini berfungsi adalah pemrosesan sinyal, menggunakan SIGUSR1 sehingga proses anak dapat membuat proses induk tahu bahwa itu dilakukan. Menggunakan SIGCHLD tidak akan berfungsi karena sebagian besar pernyataan dalam skrip akan menghasilkan sinyal SIGCHLD ke skrip shell. Saya mencoba ini mengganti perintah Anda dengan sleep 1, program menggunakan 0,28 cpu pengguna dan 0,14 cpu sistem; ini hanya ada di sekitar 400 file.

Arcege
sumber
Bagaimana 'tunggu' cukup pintar untuk mengambil file yang sama yang saat ini sedang diiterasi dan memasukkan kembali pernyataan saudara "jika"?
J Jones
Bukan waityang cukup 'pintar'; tetapi akan kembali setelah mendapatkan SIGUSR1sinyal. Anak / pekerja mengirim SIGUSR1ke orang tua, yang tertangkap ( trap), dan pengurangan $worker( trapklausa) dan kembali secara tidak normal wait, memungkinkan if [ $worker -lt $num_workers ]klausa untuk mengeksekusi.
Arcege
0

Atau cukup gunakan xargs -P, tidak perlu menginstal perangkat lunak tambahan:

find . -type f -print0 | xargs -0 -I'XXX' -P4 -n1 custom_script -input "XXX" -output "XXX.out"

Sedikit penjelasan untuk opsi:

  • -I'XXX' set string yang akan diganti dalam templat perintah dengan nama file
  • -P4 akan menjalankan 4 proses secara paralel
  • -n1 akan menempatkan hanya satu file per eksekusi walaupun dua XXX ditemukan
  • -print0dan -0bekerja bersama, memungkinkan Anda memiliki karakter khusus (seperti spasi) dalam nama file
Piotr Czapla
sumber