Параллельное выполнение конвейерных команд

16

Рассмотрим следующий сценарий. У меня есть две программы A и B. Программа A выводит в stdout строки строк, в то время как программа B обрабатывает строки из stdin. Способ использовать эти две программы, конечно:

foo @ bar: ~ $ A | В

Теперь я заметил, что это съедает только одно ядро; отсюда мне интересно:

Программы А и В совместно используют одни и те же вычислительные ресурсы? Если так, есть ли способ запустить A и B одновременно?

Еще одна вещь, которую я заметил, заключается в том, что A работает намного быстрее, чем B, поэтому мне интересно, может ли каким-то образом запускать больше программ на B и позволить им обрабатывать строки, которые A выводит параллельно.

То есть, A выводил бы свои строки, и было бы N экземпляров программ B, которые читали бы эти строки (кто бы ни читал их в первую очередь), обрабатывали их и выводили их на стандартный вывод.

Итак, мой последний вопрос:

Есть ли способ передать вывод в A среди нескольких процессов B, не заботясь о состоянии гонки и других несоответствиях, которые могут потенциально возникнуть?

Jernej
источник
1
В то время как A | B | Cпараллельно , как в отдельных процессах, в связи с характером труб (В должен ждать выхода A, C должен ждать выхода B) он все еще может быть линейным , в некоторых случаях. Это полностью зависит от того, какую продукцию они производят. Существует не так много случаев, когда многократный запуск Bмог бы сильно помочь, вполне возможно, что пример параллельного wc медленнее обычного, wcпоскольку для расщепления может потребоваться больше ресурсов, чем для обычного подсчета строк. Используйте с осторожностью.
frostschutz

Ответы:

14

Проблема split --filterв том, что выходные данные могут быть перепутаны, поэтому вы получите половину строки от процесса 1, а затем половину строки от процесса 2.

GNU Parallel гарантирует, что не будет путаницы.

Итак, предположим, что вы хотите сделать:

 A | B | C

Но этот B ужасно медленный, и поэтому вы хотите распараллелить это. Тогда вы можете сделать:

A | parallel --pipe B | C

GNU Parallel по умолчанию разделяется на \ n и имеет размер блока 1 МБ. Это можно настроить с помощью --recend и --block.

Вы можете найти больше информации о GNU Parallel по адресу: http://www.gnu.org/s/parallel/

Вы можете установить GNU Parallel всего за 10 секунд с помощью:

wget -O - pi.dk/3 | sh 

Посмотрите вступительное видео на http://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

Оле Танге
источник
1
Хотя я категорически не согласен с методом установки :-), +1, потому что ваше решение решает большинство проблем с моим.
LSerni
Этот действительно хорош. Есть ли у вас какие-либо предложения для параметров, которые будут использоваться? Я знаю, что программа А будет выводить более 1 ТБ данных примерно на 5 ГБ в минуту. Программа B обрабатывает данные в 5 раз медленнее, чем выводит A, и у меня есть 5 ядер для этой задачи.
Jernej
В настоящее время GNU Parallel может обрабатывать не более 100 МБ / с, так что вы собираетесь коснуться этого предела. Оптимальный --block-sizeбудет зависеть от объема оперативной памяти и от того, насколько быстро вы сможете начать новую B. В вашей ситуации я бы использовал --block 100Mи посмотрел, как это выполняется.
Оле Танге
@lserni Можете ли вы придумать лучший способ установки, который работает на большинстве машин UNIX и требует от пользователя аналогичного объема работы?
Оле Танге
4
Извините, я не прояснил себя. Метод установки - сценарий передан sh- отлично. Проблема заключается в передаче его sh: загрузка и запуск исполняемого кода с сайта . Имейте в виду, может быть, я просто слишком параноик, поскольку можно возразить, что пользовательский RPM или DEB - это одно и то же, и даже размещение кода на странице для копирования и вставки приведет к тому, что люди будут делать это вслепую. тем не мение.
LSerni
13

Когда вы пишете A | B, оба процесса уже работают параллельно. Если вы видите, что они используют только одно ядро, это может быть связано либо с настройками привязки к процессору (возможно, есть какой-то инструмент для запуска процесса с другой привязкой), либо потому, что одного процесса недостаточно для хранения всего ядра и системы ». Предпочитает «не распространять вычисления».

Чтобы запустить несколько B с одним A, вам нужен инструмент, например, splitс --filterопцией:

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

Это, однако, может испортить порядок строк в выходных данных, поскольку задания B не будут выполняться с одинаковой скоростью. Если это проблема, вам может понадобиться перенаправить вывод i-го файла в промежуточный файл и сшить их вместе в конце, используяcat . Это, в свою очередь, может потребовать значительного дискового пространства.

Существуют и другие параметры (например , вы могли бы ограничить каждый экземпляр B к одной линии буферизации вывода, ждать , пока весь «круглый» из Б закончила, не запустить эквивалент уменьшить до splitкарт , а также catвременный выход вместе), с различными уровнями эффективности. Опция 'round', например, только что описанная, будет ожидать завершения самого медленного экземпляра B , поэтому она будет сильно зависеть от доступной буферизации для B; [m]bufferможет помочь, а может и нет, в зависимости от операций.

Примеры

Сгенерируйте первые 1000 чисел и сосчитайте линии параллельно:

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

Если бы мы «пометили» строки, мы бы увидели, что каждая первая строка отправляется в процесс № 1, каждая пятая строка - в процесс № 5 и так далее. Более того, за то время, которое требуется splitдля запуска второго процесса, первый уже является хорошим способом в его квоте:

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

При выполнении на 2-жильную машине, seq, splitи wcпроцессы совместно сердечник; но при ближайшем рассмотрении система оставляет первые два процесса на CPU0 и делит CPU1 между рабочими процессами:

%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

Особенно обратите внимание, что splitкушает значительное количество процессоров. Это будет уменьшаться пропорционально потребностям А; то есть, если A более тяжелый процесс, чем seq, относительные накладные расходы splitуменьшатся. Но если A - очень легкий процесс, а B - довольно быстрый (так что вам нужно не больше, чем 2-3 B, чтобы держаться вместе с A), то распараллеливание с split(или трубами в целом) может не стоить этого.

LSerni
источник
Интересно, что раскол, найденный в Ubuntu, не имеет опции --filter. Какие ОС используют для этого?
Джерней
Linux OpenSuSE 12.3, с coreutils ( gnu.org/software/coreutils/manual/html_node/… ). Я попытаюсь заполучить Ubuntu, они могли бы изменить имя, чтобы приспособить его к инструменту с таким же именем.
LSerni
Вы уверены, что split --filterопция отсутствует? На моем Ubuntu 12.04-LTS ("wheezy / sid") он есть, и мои примеры работают. Не могли бы вы установить другой, splitчем в GNU coreutils?
LSerni
Спасибо за это. Мне пришлось установить более новую версию Coreutils. Кстати, я заметил, что если я запускаю программу A в одиночку, она съедает все ядро ​​(100%), если я запускаю A | Затем они вместе съедают целое ядро, обрабатывают А, съедая 15%, и обрабатывают В, съедая 85%. Вы случайно не понимаете, почему это так?
Jernej
2
Это, вероятно, из-за блокировки . Если B тяжелее, чем A, то A не может отправить свой вывод и замедляется. Другая возможность - А уступает В во время своей работы (например, диск / сеть). В другой системе вы можете увидеть, как B поглощает 100% CPU1, а A назначается 18% CPU0. Вероятно, вам нужно 85/15 ~ 5.67 = от 5 до 6 экземпляров B, чтобы получить один экземпляр A для насыщения одного ядра. Впрочем, при наличии ввода-вывода эти значения могут искажаться.
LSerni