Поширення stdin на паралельні процеси


13

У мене є завдання, яке обробляє список файлів на stdin. Час запуску програми є значним, і кількість часу, який займає кожен файл, варіюється в широких межах. Я хочу породити значну кількість цих процесів, а потім диспетчерська робота для тих, хто не зайнятий. Існує кілька різних інструментів командного рядка, які майже роблять те, що я хочу, я звузив його до двох майже працюючих варіантів:

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

Проблема полягає в тому, splitщо це чистий кругообіг, тому один з процесів відстає і залишається позаду, затримуючи завершення всієї операції; хоча parallelхоче породжувати один процес на N рядків або байтів введення, і я закінчую витрачати занадто багато часу на стартові накладні витрати.

Чи є щось подібне, яке повторно використовуватиме процеси та лінії подачі для тих, які б процеси не розблокували stdins?


Звідки ця splitкоманда? Назва суперечить стандартній утиліті для обробки тексту.
Жил "ТАК - перестань бути злим"

@ Gilles, це GNU: "розділити (GNU coreutils) 8.13" . Використання його як дивної альтернативи xargs, мабуть, не призначене для використання, але це найближче до того, що я хочу знайти.
BCoates

2
Я думав про це, і принциповою проблемою є те, що примірник myjobготовий отримати більше вкладів. Немає можливості знати, що програма готова обробити більше вводу, все, що ви можете знати, це те, що десь буфер (буфер труби, буфер stdio) готовий отримати більше вводу. Чи можете ви домовитись, щоб ваша програма надіслала якийсь запит (наприклад, відображає підказку), коли вона готова?
Жил "ТАК - перестань бути злим"

Якщо припустити, що програма не використовує bufering на stdin, файлова система FUSE, яка реагує на readдзвінки, зробить свою справу. Це досить велике завдання програмування.
Жил "ТАК - перестань бути злим"

чому ви використовуєте -l 1в parallelаргументах? IIRC, який повідомляє паралельно обробляти один рядок введення на роботу (тобто одне ім'я файлу на вилку myjob, так багато накладних накладних витрат).
cas

Відповіді:


1

Це не виглядає можливим у такому загальному випадку. Це означає, що у вас є буфер для кожного процесу, і ви можете спостерігати за буферами ззовні, щоб вирішити, куди поставити наступний запис (планування) ... Звичайно, ви можете щось написати (або використовувати пакетну систему, як slurm)

Але залежно від того, що це за процес, ви, можливо, зможете попередньо обробити введення. Наприклад, якщо ви хочете завантажити файли, оновити записи з БД чи подібні, але 50% з них в кінцевому підсумку буде пропущено (і для цього у вас велика різниця в обробці залежно від вводу), просто встановіть попередній процесор що підтверджує, які записи триватимуть довго (файл існує, дані були змінені тощо), тому все, що надходить з іншого боку, гарантовано забирає досить рівну кількість часу. Навіть якщо евристика не є досконалою, ви можете досягти значного покращення. Ви можете скинути інші у файл та обробити потім таким же чином.

Але це залежить від вашого випадку використання.


1

Ні, немає загального рішення. Ваш диспетчер повинен знати, коли кожна програма готова прочитати інший рядок, і я не знаю жодного стандарту, який би це дозволяв. Все, що ви можете зробити, це поставити рядок на STDOUT і чекати, щоб щось спожити; насправді не є хорошим способом для виробника на трубопроводі сказати, чи готовий наступний споживач чи ні.


0

Я не думаю, що так. У моєму улюбленому журналі колись була стаття про програмування bash, яка робила те, що ти хочеш. Я готовий вірити, що якби були інструменти для цього, вони б їх згадали. Отже, ви хочете чогось узгоджувати:

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

Очевидно, ви можете змінити виклик на фактичний робочий сценарій на свій смак. Журнал I mentionen спочатку робить такі речі, як налаштування труб та власне запуск робочих ниток. Перевірте mkfifoце, але цей маршрут є набагато складнішим, оскільки робочі процеси повинні сигналізувати головному процесу про те, що вони готові отримати більше даних. Отже, вам потрібно одна фіфо для кожного робочого процесу для надсилання даних і одна фіфо для головного процесу для отримання матеріалів від робітників.

ВІДМОВА ВІДМОВА Я написав цей сценарій у верхній частині голови. Можливо, є деякі проблеми з синтаксисом.


1
Схоже, це не відповідає вимогам: ви починаєте інший примірник програми для кожного елемента.
Жил "ТАК - перестань бути злим"

Зазвичай, переважно find . -type f | while read i, ніж використовувати for i in $(find . -type f).

0

Для GNU Parallel ви можете встановити розмір блоку за допомогою --block. Однак потрібно, щоб у вас було достатньо пам’яті для збереження 1 блоку пам’яті для кожного із запущених процесів.

Я розумію, що це не саме те, що ви шукаєте, але це може бути прийнятною обробкою поки що.

Якщо ваші завдання в середньому займають один і той же час, ви, можливо, зможете використовувати mbuffer:

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

0

Спробуйте це:

mkfifo для кожного процесу.

Потім повісьте tail -f | myjobна кожну фіфо.

Наприклад, налаштування робітників (мійобійок)

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

Залежно від вашої програми (myjob), ви можете використовувати завдання -s для пошуку зупинених завдань. В іншому випадку перерахуйте процеси, відсортовані за процесором, і виберіть той, що споживає найменше ресурсів. Мають сам звіт про роботу, наприклад, встановивши прапор у файловій системі, коли він хоче більше роботи.

Припускаючи, що робота зупиняється під час очікування введення, використовуйте

jobs -sl наприклад, щоб дізнатись про зупинену роботу та призначити її роботу, наприклад

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

Я тестував це з

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

Це, маю визнати, було просто приготовлено так ymmv.


0

Для вирішення цього дійсно потрібно механізм черг певного типу.

Чи можливо, щоб завдання, зчитуючи свої дані з черги, наприклад, черга повідомлень SYSV, а потім програми, що працюють паралельно, просто натискали значення на чергу?

Інша можливість полягає у використанні каталогів для черги, як це:

  1. висновок знаходження створює символьне посилання на кожен файл для обробки в каталозі, pending
  2. кожен робочий процес виконує mvперший файл, який він бачить у каталозі, до каталогу братів pending, назви inprogress.
  3. якщо завдання успішно переміщує файл, воно виконує обробку; в іншому випадку він повертається до пошуку та переміщення іншого імені файлу зpending

0

пояснюючи відповідь @ ash, ви можете використовувати чергу повідомлень SYSV для розподілу роботи. Якщо ви не хочете писати власну програму на C, є утиліта, яка називається ipcmd. Ось що я зібрав для передачі результатів find $DIRECTORY -type fдля $PARALLELкількох процесів:

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

Ось пробний запуск:

$ 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

Якщо ви не зможете оцінити, як довго буде оброблятися певний вхідний файл, і робочі процеси не мають способу звітувати перед планувальником (як це відбувається у звичайних сценаріях паралельних обчислень - часто через MPI ), вам, як правило, не пощастило - або сплатити штраф деяким працівникам, які обробляють вхід довше, ніж інші (через нерівність введення), або сплачують штраф за нерестування одного нового процесу за кожен вхідний файл.


0

Паралель GNU змінився за останні 7 років. Тож сьогодні це можна зробити:

Цей приклад показує, що для процесів 11 і 10 приділяється більше блоків, ніж процес 4 і 5, оскільки 4 і 5 читаються повільніше:

seq 1000000 |
  parallel -j8 --tag --roundrobin --pipe --block 1k 'pv -qL {}0000 | wc' ::: 11 4 5 6 9 8 7 10
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.