Menjalankan perintah pipa secara paralel

16

Pertimbangkan skenario berikut. Saya memiliki dua program A dan B. Output Program A ke stdout string, sedangkan program B memproses baris dari stdin. Cara menggunakan kedua program ini tentu saja:

foo @ bar: ~ $ A | B

Sekarang saya perhatikan bahwa ini memakan hanya satu inti; karenanya saya bertanya-tanya:

Apakah program A dan B berbagi sumber daya komputasi yang sama? Jika demikian, apakah ada cara untuk menjalankan A dan B secara bersamaan?

Hal lain yang saya perhatikan adalah bahwa A berjalan jauh lebih cepat daripada B, maka saya bertanya-tanya apakah bisa menjalankan lebih banyak program B dan membiarkan mereka memproses garis-garis yang dihasilkan A secara paralel.

Yaitu, A akan menampilkan baris-barisnya, dan akan ada N contoh program B yang akan membaca baris-baris ini (siapa pun yang membacanya pertama kali) memprosesnya dan mengeluarkannya di stdout.

Jadi pertanyaan terakhir saya adalah:

Apakah ada cara untuk menyalurkan output ke A di antara beberapa proses B tanpa harus menjaga kondisi ras dan inkonsistensi lain yang berpotensi timbul?

Jernej
sumber
1
Meskipun A | B | Cparalel seperti dalam proses terpisah, karena sifat pipa (B harus menunggu output dari A, C harus menunggu output dari B) mungkin masih linier dalam beberapa kasus. Ini sepenuhnya tergantung pada jenis output yang mereka hasilkan. Tidak banyak kasus di mana menjalankan banyak Bakan banyak membantu, sangat mungkin bahwa contoh wc paralel lebih lambat dari biasanya wckarena pemisahan dapat mengambil lebih banyak sumber daya daripada menghitung garis normal. Gunakan dengan hati-hati.
frostschutz

Jawaban:

14

Masalah dengan split --filteradalah bahwa output dapat digabungkan, sehingga Anda mendapatkan setengah garis dari proses 1 diikuti oleh setengah garis dari proses 2.

GNU Parallel menjamin tidak akan ada mixup.

Jadi anggap Anda ingin melakukan:

 A | B | C

Tapi B itu sangat lambat, dan dengan demikian Anda ingin memparalelkannya. Maka Anda dapat melakukan:

A | parallel --pipe B | C

GNU Paralel secara default terbagi pada \ n dan ukuran blok 1 MB. Ini dapat disesuaikan dengan --recend dan --block.

Anda dapat menemukan lebih banyak tentang GNU Parallel di: http://www.gnu.org/s/parallel/

Anda dapat menginstal GNU Parallel hanya dalam 10 detik dengan:

wget -O - pi.dk/3 | sh 

Tonton video intro di http://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

Ole Tange
sumber
1
Sementara saya sangat tidak setuju pada metode instalasi :-), +1 karena solusi Anda memecahkan sebagian besar masalah dengan masalah saya.
LSerni
Yang ini memang bagus. Apakah Anda juga punya saran untuk menggunakan parameter? Saya tahu program A akan menampilkan lebih dari 1 TB data sekitar 5GB per menit. Program B memproses data 5 kali lebih lambat dari output A dan saya memiliki 5 core yang saya inginkan untuk tugas ini.
Jernej
GNU Paralel saat ini paling banyak dapat menangani sekitar 100 MB / s, sehingga Anda akan menyentuh batas itu. Yang optimal --block-sizeakan tergantung pada jumlah RAM dan seberapa cepat Anda dapat memulai yang baru B. Dalam situasi Anda, saya akan menggunakan --block 100Mdan melihat bagaimana itu dilakukan.
Ole Tange
@lserni Bisakah Anda membuat metode instalasi yang lebih baik, yang bekerja pada sebagian besar mesin UNIX dan membutuhkan jumlah pekerjaan yang sama dari pengguna?
Ole Tange
4
Maaf, saya tidak menjelaskan. Metode instalasi - script yang diteruskan ke sh- sangat bagus. Masalahnya terletak pada meneruskannya ke sh: mengunduh dan menjalankan kode yang dapat dieksekusi dari sebuah situs . Pikiran Anda, mungkin saya hanya terlalu paranoid, karena orang bisa keberatan bahwa RPM atau DEB yang dibuat pada dasarnya adalah hal yang sama, dan bahkan memposting kode pada halaman yang akan disalin dan ditempelkan akan mengakibatkan orang melakukan hal itu secara membabi buta bagaimanapun.
LSerni
13

Saat Anda menulis A | B, kedua proses sudah berjalan secara paralel. Jika Anda melihatnya menggunakan hanya satu inti, itu mungkin karena salah satu pengaturan afinitas CPU (mungkin ada beberapa alat untuk menelurkan proses dengan afinitas berbeda) atau karena satu proses tidak cukup untuk menampung seluruh inti, dan sistem " lebih suka "tidak menyebar komputasi.

Untuk menjalankan beberapa B dengan satu A, Anda memerlukan alat seperti splitdengan --filteropsi:

A | split [OPTIONS] --filter="B"

Ini, bagaimanapun, bertanggung jawab untuk mengacaukan urutan garis dalam output, karena pekerjaan B tidak akan berjalan dengan kecepatan yang sama. Jika ini merupakan masalah, Anda mungkin perlu mengarahkan ulang keluaran ke-4 ke file perantara dan menjahitnya bersama-sama di akhir menggunakan cat. Ini, pada gilirannya, mungkin memerlukan ruang disk yang cukup besar.

Pilihan lain ada (misalnya Anda bisa membatasi setiap contoh dari B ke output line-buffered tunggal, menunggu sampai seluruh "putaran" dari B telah selesai, jalankan setara dengan mengurangi untuk split's peta , dan catoutput sementara bersama-sama), dengan berbagai tingkat efisiensi. Opsi 'round' yang baru saja dijelaskan misalnya akan menunggu instance B yang paling lambat selesai, jadi itu akan sangat tergantung pada buffering yang tersedia untuk B; [m]buffermungkin membantu, atau mungkin tidak, tergantung pada apa operasinya.

Contohnya

Hasilkan 1000 angka pertama dan hitung garis secara paralel:

seq 1 1000 | split -n r/10 -u --filter="wc -l"
100
100
100
100
100
100
100
100
100
100

Jika kita "menandai" garis, kita akan melihat bahwa setiap baris pertama dikirim ke proses # 1, setiap baris kelima untuk memproses # 5 dan seterusnya. Selain itu, dalam waktu yang dibutuhkan splituntuk menelurkan proses kedua, yang pertama sudah merupakan cara yang baik untuk kuota:

seq 1 1000 | split -n r/10 -u --filter="sed -e 's/^/$RANDOM - /g'" | head -n 10
19190 - 1
19190 - 11
19190 - 21
19190 - 31
19190 - 41
19190 - 51
19190 - 61
19190 - 71
19190 - 81

Ketika mengeksekusi pada mesin 2-core seq,, splitdan wcproses berbagi core; tetapi melihat lebih dekat, sistem meninggalkan dua proses pertama pada CPU0, dan membagi CPU1 di antara proses pekerja:

%Cpu0  : 47.2 us, 13.7 sy,  0.0 ni, 38.1 id,  1.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  : 15.8 us, 82.9 sy,  0.0 ni,  1.0 id,  0.0 wa,  0.3 hi,  0.0 si,  0.0 st
  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM     TIME+ COMMAND
 5314 lserni    20   0  4516  568  476 R 23.9  0.0   0:03.30 seq
 5315 lserni    20   0  4580  720  608 R 52.5  0.0   0:07.32 split
 5317 lserni    20   0  4520  576  484 S 13.3  0.0   0:01.86 wc
 5318 lserni    20   0  4520  572  484 S 14.0  0.0   0:01.88 wc
 5319 lserni    20   0  4520  576  484 S 13.6  0.0   0:01.88 wc
 5320 lserni    20   0  4520  576  484 S 13.3  0.0   0:01.85 wc
 5321 lserni    20   0  4520  572  484 S 13.3  0.0   0:01.84 wc
 5322 lserni    20   0  4520  576  484 S 13.3  0.0   0:01.86 wc
 5323 lserni    20   0  4520  576  484 S 13.3  0.0   0:01.86 wc
 5324 lserni    20   0  4520  576  484 S 13.3  0.0   0:01.87 wc

Perhatikan khususnya bahwa splitmemakan sejumlah besar CPU. Ini akan berkurang secara proporsional dengan kebutuhan A; yaitu, jika A adalah proses yang lebih berat daripada seq, overhead relatif splitakan berkurang. Tetapi jika A adalah proses yang sangat ringan dan B cukup cepat (sehingga Anda tidak perlu lebih dari 2-3 B untuk tetap bersama dengan A), maka memparalelkan dengan split(atau pipa pada umumnya) mungkin tidak sepadan.

LSerni
sumber
Menarik bahwa split yang ditemukan di Ubuntu tidak memiliki opsi --filter. OS macam apa yang digunakan untuk ini?
Jernej
Linux OpenSuSE 12.3, dengan coreutils ( gnu.org/software/coreutils/manual/html_node/… ). Saya akan mencoba dan mendapatkan Ubuntu, mereka mungkin telah mengubah nama untuk mengakomodasi beberapa alat yang bernama sama.
LSerni
Apakah Anda yakin tentang split --filteropsi yang hilang? Di Ubuntu 12.04-LTS ("wheezy / sid") saya, ada di sana, dan contoh saya berfungsi. Bisakah Anda menginstal yang berbeda splitdari yang ada di GNU coreutils?
LSerni
Terima kasih untuk ini. Saya harus menginstal Coreutils versi yang lebih baru. BTW, saya perhatikan bahwa jika saya menjalankan program A saja ia memakan seluruh inti (100%) jika saya menjalankan A | B kemudian mereka bersama-sama makan seluruh inti, memproses A makan 15% dan memproses B makan 85% .. Apakah Anda kebetulan melihat mengapa demikian?
Jernej
2
Ini kemungkinan karena pemblokiran . Jika B lebih berat dari A, maka A tidak dapat mengirim outputnya dan diperlambat. Kemungkinan lain adalah A menghasilkan ke B selama operasinya (mis. Disk / net). Pada sistem yang berbeda, Anda mungkin melihat B melahap 100% CPU1 dan A ditugaskan 18% dari CPU0. Anda mungkin membutuhkan 85/15 ~ 5.67 = antara 5 dan 6 instance B untuk mendapatkan satu instance A untuk menjenuhkan satu inti. I / O, jika ada, mungkin membelokkan nilai-nilai ini.
LSerni