Stdin'i paralel süreçlere yaymak


13

Stdin dosya listesini işleyen bir görevim var. Programın başlatma süresi büyüktür ve her dosyanın aldığı zaman büyük ölçüde değişir. Bu işlemlerin önemli bir kısmını oluşturmak istiyorum, sonra işi hangisi meşgul değilse ona gönderiyorum. Neredeyse istediğimi yapan birkaç farklı komut satırı aracı var, neredeyse iki çalışma seçeneğine daralttım:

find . -type f | split -n r/24 -u --filter="myjob"
find . -type f | parallel --pipe -u -l 1 myjob

Sorun şu ki split, saf bir yuvarlak robin yapıyor, bu yüzden süreçlerden biri geride kalıyor ve geride kalıyor ve tüm operasyonun tamamlanmasını geciktiriyor; ise parallelistekleri N hatları başına bir işlem spawn veya giriş bayt ve başlangıç yükü üzerinde çok fazla zaman harcama kadar rüzgar.

Hangi süreçlerin engellenmemiş stdine sahip olduğu süreçleri ve besleme hatlarını yeniden kullanacak böyle bir şey var mı?


Bu splitkomut nereden geliyor? Ad, standart metin işleme yardımcı programıyla çakışıyor .
Gilles 'SO- kötü olmayı bırak

@Gilles, bu GNU bir: "bölünmüş (GNU coreutils) 8.13" . Bunu xargs'a garip bir alternatif olarak kullanmak muhtemelen amaçlanan kullanım değildir, ancak bulduğum şeye en yakın olanıdır.
12'de

2
Bunu düşünüyorum ve temel bir sorun, bir örneğinin myjobdaha fazla girdi almaya hazır olduğunu bilmektir . Bir programın daha fazla girdiyi işlemeye hazır olduğunu bilmenin bir yolu yoktur, bildiğiniz tek şey bir yerlerde bir tamponun (bir boru tamponu, bir stdio tamponu) daha fazla girdi almaya hazır olmasıdır. Programınızın hazır olduğunda bir tür istek göndermesini ayarlayabilir misiniz (örneğin bir bilgi istemi görüntüleyebilir)?
Gilles 'SO- kötü olmayı kes'

Programın stdin üzerinde bufering kullanmadığı varsayılarak, readçağrılara tepki veren bir FUSE dosya sistemi işinizi görecektir. Bu oldukça büyük bir programlama çabası.
Gilles 'SO- kötü olmayı kes'

Neden Kullandığınız -l 1içinde parallelargs? İş başına bir girdi satırı işlemeyi paralel olarak anlatan IIRC (yani, myjob çatal başına bir dosya adı, bu nedenle çok fazla başlangıç ​​yükü).
cas

Yanıtlar:


1

Böyle genel bir durumda mümkün görünmüyor. Her işlem için bir ara belleğe sahip olduğunuz anlamına gelir ve bir sonraki girdiyi nereye koyacağınıza karar vermek için tamponları dışarıdan izleyebilirsiniz (zamanlama) ... Tabii ki bir şeyler yazabilirsiniz (veya slurm gibi bir toplu sistem kullanabilirsiniz)

Ancak, işlemin ne olduğuna bağlı olarak, girdiyi önceden işleyebilirsiniz. Örneğin, dosyaları indirmek, bir DB veya benzerlerinden girişleri güncellemek istiyorsanız, ancak bunların% 50'si atlanır (ve bu nedenle girdiye bağlı olarak büyük bir işlem farkınız vardır), o zaman bir ön işlemci kurun hangi girişlerin uzun süreceğini doğrular (dosya var, veriler değişti vb.), böylece diğer taraftan gelen her şeyin oldukça eşit bir süre alacağı garanti edilir. Buluşsal yöntem mükemmel olmasa bile, önemli bir gelişme elde edebilirsiniz. Diğerlerini bir dosyaya dökebilir ve daha sonra aynı şekilde işleyebilirsiniz.

Ancak bu sizin kullanım durumunuza bağlıdır.


1

Hayır, genel bir çözüm yok. Görev dağıtıcınızın her programın başka bir satırı okumaya hazır olduğunu bilmesi gerekir ve buna izin veren bir standart yoktur. Yapabileceğiniz tek şey STDOUT'a bir satır koymak ve bir şeyin tükenmesini beklemek; bir boru hattındaki üreticinin bir sonraki tüketicinin hazır olup olmadığını söylemesinin gerçekten iyi bir yolu yoktur.


0

Ben öyle düşünmüyorum. En sevdiğim dergide bash programlama ile ilgili bir makale yazdı. Bunu yapmak için araçlar olsaydı onlardan bahsedeceklerine inanmaya hazırım. Yani şu çizgilerle bir şey istersiniz:

set -m # enable job control
max_processes=8
concurrent_processes=0

child_has_ended() { concurrent_processes=$((concurrent_processes - 1)) }

trap child_has_ended SIGCHLD # that's magic calling our bash function when a child processes ends

for i in $(find . -type f)
do
  # don't do anything while there are max_processes running
  while [ ${concurrent_processes} -ge ${max_processes}]; do sleep 0.5; done 
  # increase the counter
  concurrent_processes=$((concurrent_processes + 1))
  # start a child process to actually deal with one file
  /path/to/script/to/handle/one/file $i &
done

Açıkçası, gerçek çalışma komut dosyasının çağrısını beğeninize göre değiştirebilirsiniz. Bahsettiğim dergi, başlangıçta boruları kurmak ve aslında işçi ipliklerine başlamak gibi şeyler yapıyor. mkfifoBunu kontrol edin , ancak çalışan süreçlerin daha fazla veri almaya hazır olduklarını ana işleme işaret etmeleri gerektiğinden bu rota çok daha karmaşıktır. Dolayısıyla, her bir çalışan işlem için veri göndermek için bir fifo ve ana işlemin işçilerden bir şeyler almak için bir fifo gerekir.

YASAL UYARI Bu senaryoyu başımın üstünden yazdım. Bazı sözdizimi sorunları olabilir.


1
Bu, gereksinimleri karşılamıyor gibi görünüyor: her bir öğe için programın farklı bir örneğini başlatıyorsunuz.
Gilles 'SO- kötü olmayı bırak

Genellikle kullanmak find . -type f | while read iyerine tercih edilir for i in $(find . -type f).

0

GNU Parallel için --block komutunu kullanarak blok boyutunu ayarlayabilirsiniz. Ancak, çalışan işlemlerin her biri için 1 bloğu bellekte tutmak için yeterli belleğiniz olmasını gerektirir.

Bunun tam olarak aradığınız şey olmadığını anlıyorum, ancak şimdilik kabul edilebilir bir çözüm olabilir.

Görevleriniz ortalama olarak aynı süreyi alıyorsa, mbuffer'ı kullanabilirsiniz:

find . -type f | split -n r/24 -u --filter="mbuffer -m 2G | myjob"

0

Bunu dene:

mkfifo her işlem için.

Sonra tail -f | myjobher bir fifo'ya asın .

Örneğin işçilerin kurulması (myjob süreçleri)

mkdir /tmp/jobs
for X in 1 2 3 4
do
   mkfifo pipe$X
   tail -f pipe$X | myjob &
   jobs -l| awk '/pipe'$X'/ {print $2, "'pipe$X'"}' >> pipe-job-mapping
done

Uygulamanıza (myjob) bağlı olarak eb durdurulan işleri bulmak için işleri -s kullanabilirsiniz. Aksi takdirde CPU'ya göre sıralanmış işlemleri listeleyin ve en az kaynak tüketen işlemleri seçin. Örneğin, daha fazla çalışma istediğinde dosya sisteminde bir bayrak ayarlayarak işin raporunu almak.

Giriş beklenirken işin durduğu varsayılarak,

jobs -sl durdurulan bir işin pid'ini bulmak ve işe atamak için, örneğin

grep "^$STOPPED_PID" pipe-to-job-mapping | while read PID PIPE
do
   cat workset > $PIPE
done

Bunu ile test ettim

garfield:~$ cd /tmp
garfield:/tmp$ mkfifo f1
garfield:/tmp$ mkfifo f2
garfield:/tmp$ tail -f f1 | sed 's/^/1 /' &
[1] 21056
garfield:/tmp$ tail -f f2 | sed 's/^/2 /' &
[2] 21058
garfield:/tmp$ echo hello > f1
1 hello
garfield:/tmp$ echo what > f2
2 what
garfield:/tmp$ echo yes > f1
1 yes

Bu itiraf etmeliyim ki sadece ymmv uydurulmuş.


0

Bunu çözmek için gerçekten ihtiyaç duyulan şey, bir tür kuyruk mekanizmasıdır.

İşlerin girdilerini SYSV ileti kuyruğu gibi bir Kuyruktan okuması ve daha sonra programların paralel olarak çalıştırılması değerleri basit bir şekilde kuyruğa aktarmayı mümkün mü?

Başka bir olasılık, kuyruk için aşağıdaki gibi bir dizin kullanmaktır:

  1. find çıktısı bir dizinde işlemek için her dosyaya bir sembolik bağlantı oluşturur, pending
  2. her iş süreci mv, dizinde gördüğü ilk dosyadan birini pendingadlı adlı bir kardeş dizine gerçekleştirir inprogress.
  3. iş dosyayı başarılı bir şekilde taşırsa, işlemi gerçekleştirir; Aksi takdirde, başka bir dosya adı bulmak ve taşımak için geri döner.pending

0

@ ash'in cevabına yanıt vererek, işi dağıtmak için bir SYSV mesaj kuyruğu kullanabilirsiniz. C'de kendi programınızı yazmak istemiyorsanız ipcmd, yardımcı olabilecek bir yardımcı program vardır . İşte çıktısını geçmek araya ne find $DIRECTORY -type fkadar $PARALLELsüreçlerin sayısı:

set -o errexit
set -o nounset

export IPCMD_MSQID=$(ipcmd msgget)

DIRECTORY=$1
PARALLEL=$2

# clean up message queue on exit
trap 'ipcrm -q $IPCMD_MSQID' EXIT

for i in $(seq $PARALLEL); do
   {
      while true
      do
          message=$(ipcmd msgrcv) || exit
          [ -f $message ] || break
          sleep $((RANDOM/3000))
      done
   } &
done

find "$DIRECTORY" -type f | xargs ipcmd msgsnd

for i in $(seq $PARALLEL); do
   ipcmd msgsnd "/dev/null/bar"
done
wait

İşte bir test çalıştırması:

$ for i in $(seq 20 10 100) ; do time parallel.sh /usr/lib/ $i ; done
parallel.sh /usr/lib/ $i  0.30s user 0.67s system 0% cpu 1:57.23 total
parallel.sh /usr/lib/ $i  0.28s user 0.69s system 1% cpu 1:09.58 total
parallel.sh /usr/lib/ $i  0.19s user 0.80s system 1% cpu 1:05.29 total
parallel.sh /usr/lib/ $i  0.29s user 0.73s system 2% cpu 44.417 total
parallel.sh /usr/lib/ $i  0.25s user 0.80s system 2% cpu 37.353 total
parallel.sh /usr/lib/ $i  0.21s user 0.85s system 3% cpu 32.354 total
parallel.sh /usr/lib/ $i  0.30s user 0.82s system 3% cpu 28.542 total
parallel.sh /usr/lib/ $i  0.27s user 0.88s system 3% cpu 30.219 total
parallel.sh /usr/lib/ $i  0.34s user 0.84s system 4% cpu 26.535 total

0

Belirli bir girdi dosyasının ne kadar süreyle işleneceğini tahmin edemezseniz ve çalışan işlemlerin zamanlayıcıya rapor vermenin bir yolu yoksa (normal paralel hesaplama senaryolarında olduğu gibi - genellikle MPI aracılığıyla ), genellikle şansınız kalmaz - girdiyi işleyen bazı işçilerin cezalarını diğerlerinden daha uzun (girdi eşitsizliği nedeniyle) veya her girdi dosyası için tek bir yeni süreç oluşturma cezasını ödeyin.


0

GNU Parallel son 7 yılda değişti. Yani bugün bunu yapabilir:

Bu örnek, işlem 11 ve 10'a işlem 4 ve 5'ten daha fazla blok verildiğini gösterir, çünkü 4 ve 5 daha yavaş okunur:

seq 1000000 |
  parallel -j8 --tag --roundrobin --pipe --block 1k 'pv -qL {}0000 | wc' ::: 11 4 5 6 9 8 7 10
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.