Równoległe wykonywanie poleceń potokowych

16

Rozważ następujący scenariusz. Mam dwa programy A i B. Program A wyprowadza na standardowe linie ciągów, a program B na linie standardowe ze standardowego wejścia. Oczywiście sposobem na użycie tych dwóch programów jest:

foo @ bar: ~ $ A | b

Teraz zauważyłem, że zjada to tylko jeden rdzeń; stąd zastanawiam się:

Czy programy A i B współużytkują te same zasoby obliczeniowe? Jeśli tak, to czy istnieje sposób na jednoczesne uruchomienie A i B?

Inną rzeczą, którą zauważyłem, jest to, że A działa znacznie szybciej niż B, dlatego zastanawiam się, czy mógłby jakoś uruchomić więcej programów B i pozwolić im przetwarzać linie, które A wyprowadza równolegle.

Oznacza to, że A wypisuje swoje wiersze i byłoby N instancji programów B, które czytałyby te wiersze (ktokolwiek je czyta jako pierwszy) przetwarzałyby je i wypisywały na standardowym wyjściu.

Więc moje ostatnie pytanie brzmi:

Czy istnieje sposób na skierowanie wyjścia do A pomiędzy kilkoma procesami B bez konieczności dbania o warunki wyścigu i inne niespójności, które mogą wystąpić?

Jernej
źródło
1
Chociaż A | B | Cjest równoległy, jak w oddzielnych procesach, ze względu na charakter rur (B musi czekać na wyjście A, C musi czekać na wyjście B), w niektórych przypadkach może być liniowy. Zależy to całkowicie od rodzaju produkcji, jaką wytwarzają. Nie ma wielu przypadków, w których uruchomienie wielu Bpomógłoby bardzo, jest całkiem możliwe, że przykład równoległego wc jest wolniejszy niż normalny, wcponieważ dzielenie może zająć więcej zasobów niż normalne liczenie linii. Używaj ostrożnie.
frostschutz

Odpowiedzi:

14

Problem split --filterpolega na tym, że dane wyjściowe mogą być pomieszane, więc otrzymujesz pół linii z procesu 1, a następnie pół linii z procesu 2.

GNU Parallel gwarantuje, że nie będzie pomieszania.

Załóż więc, że chcesz:

 A | B | C

Ale to B jest strasznie wolne i dlatego chcesz to zrównoważyć. Następnie możesz zrobić:

A | parallel --pipe B | C

GNU Parallel domyślnie dzieli \ n i wielkość bloku 1 MB. Można to zmienić za pomocą --recend i --block.

Możesz znaleźć więcej informacji na temat GNU Parallel na: http://www.gnu.org/s/parallel/

Możesz zainstalować GNU Parallel w zaledwie 10 sekund za pomocą:

wget -O - pi.dk/3 | sh 

Obejrzyj film wprowadzający na stronie http://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

Ole Tange
źródło
1
Chociaż zdecydowanie nie zgadzam się na metodę instalacji :-), +1, ponieważ twoje rozwiązanie rozwiązuje większość problemów z moimi.
LSerni
Ten jest naprawdę ładny. Czy masz również jakieś sugestie dotyczące parametrów, które należy zastosować? Wiem, że program A wyda więcej niż 1 TB danych około 5 GB na minutę. Program B przetwarza dane 5 razy wolniej niż dane wyjściowe A i mam do dyspozycji 5 rdzeni do tego zadania.
Jernej
GNU Parallel może obecnie obsługiwać najwyżej około 100 MB / s, więc zamierzasz dotknąć tego limitu. Optymalny --block-sizebędzie zależeć od ilości pamięci RAM i tego, jak szybko można rozpocząć nową B. W twojej sytuacji skorzystam --block 100Mi zobaczę, jak to działa.
Ole Tange
@lserni Czy możesz wymyślić lepszą metodę instalacji, która działa na większości maszyn z systemem UNIX i wymaga podobnej pracy ze strony użytkownika?
Ole Tange
4
Przepraszam, nie wyraziłem się jasno. Metoda instalacji - przekazany skrypt sh- jest świetna. Problem polega na przekazaniu go do sh: pobieranie i uruchamianie kodu wykonywalnego z witryny . Pamiętaj, może po prostu jestem zbyt paranoikiem, ponieważ można sprzeciwić się, że niestandardowe RPM lub DEB to w zasadzie to samo, a nawet opublikowanie kodu na stronie do skopiowania i wklejenia spowoduje, że ludzie zrobią to na ślepo tak czy siak.
LSerni
13

Kiedy piszesz A | B, oba procesy działają już równolegle. Jeśli widzisz, że używają tylko jednego rdzenia, prawdopodobnie wynika to z jednego z ustawień koligacji procesora (być może istnieje jakieś narzędzie do odrodzenia procesu o innym powinowactwie) lub dlatego, że jeden proces nie wystarcza do utrzymania całego rdzenia, a system „ woli „nie rozpowszechniać informatyki.

Aby uruchomić kilka B z jednym A, potrzebujesz narzędzia takiego jak splitz --filteropcją:

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

Może to jednak popsuć kolejność linii na wyjściu, ponieważ zadania B nie będą działały z tą samą prędkością. Jeśli jest to problem, może być konieczne przekierowanie wyjścia B i-tego do pliku pośredniego i połączenie ich na końcu za pomocą cat. To z kolei może wymagać znacznego miejsca na dysku.

Istnieją inne opcje (np można ograniczyć każdą instancję B do jednego wyjścia liniowego buforowane, poczekaj, aż cała „okrągły” z B zakończy, uruchom odpowiednik zmniejszyć do split„s mapę i catwyjście tymczasowe razem), o różnym poziomie wydajności. Opisana na przykład opcja „round” będzie czekać na zakończenie najwolniejszej instancji B , więc będzie w dużym stopniu zależna od dostępnego buforowania dla B; [m]buffermoże pomóc lub nie, w zależności od operacji.

Przykłady

Wygeneruj pierwsze 1000 liczb i policz linie równolegle:

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

Gdybyśmy „oznaczyli” linie, zobaczylibyśmy, że każda pierwsza linia jest wysyłana do przetwarzania nr 1, każda piąta linia do przetwarzania nr 5 i tak dalej. Co więcej, w czasie potrzebnym splitna odrodzenie drugiego procesu, pierwszy jest już dobrym sposobem na przydzielenie limitu:

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

Podczas wykonywania na maszynie 2-rdzeniowej seq, splita wcprocesy współużytkują rdzenie; ale patrząc bliżej, system pozostawia pierwsze dwa procesy na CPU0 i dzieli CPU1 na procesy robocze:

%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

Zwróć uwagę, że splitzjada znaczną ilość procesora. Zmniejszy się proporcjonalnie do potrzeb A; tj. jeśli A jest procesem cięższym niż seq, względny narzut splitzmniejszy się. Ale jeśli A jest bardzo lekkim procesem, a B jest dość szybki (tak że nie potrzebujesz więcej niż 2-3 B, aby zachować zgodność z A), wówczas równoległe stosowanie split(lub ogólnie rur) może nie być tego warte.

LSerni
źródło
Ciekawe, że podział znaleziony w Ubuntu nie ma opcji --filter. Jakiego systemu operacyjnego używają do tego?
Jernej
Linux OpenSuSE 12.3, z coreutils ( gnu.org/software/coreutils/manual/html_node/… ). Spróbuję zdobyć Ubuntu, mogliby zmienić nazwę, aby dostosować się do jakiegoś narzędzia o podobnej nazwie.
LSerni
Czy na pewno split --filterbrakuje Ci opcji? Na moim Ubuntu 12.04-LTS („wheezy / sid”) jest tam i moje przykłady działają. Czy mógłbyś zainstalować inny splitniż ten w jądrach GNU?
LSerni,
Dzięki za to. Musiałem zainstalować nowszą wersję Coreutils. BTW, zauważyłem, że jeśli uruchamiam sam program A, to zjadam cały rdzeń (100%), jeśli uruchamiam A | B następnie jedzą razem cały rdzeń, proces A je 15%, a proces B je 85%. Czy zdajesz sobie sprawę, dlaczego tak się dzieje?
Jernej
2
Jest to prawdopodobnie spowodowane blokowaniem . Jeśli B jest cięższy od A, A nie może wysłać swojej mocy wyjściowej i jest spowolniony. Inną możliwością jest poddanie się A podczas jego działania (np. Dysk / sieć). W innym systemie możesz zobaczyć, jak B pożera 100% CPU1, a A ma 18% CPU0. Prawdopodobnie potrzebujesz 85/15 ~ 5,67 = od 5 do 6 instancji B, aby uzyskać pojedynczą instancję A w celu nasycenia jednego rdzenia. I / O, jeśli jest obecny, może jednak wypaczać te wartości.
LSerni