Tworzenie pojedynczego strumienia wyjściowego z trzech innych strumieni wytwarzanych równolegle

10

Mam trzy rodzaje danych, które są w różnych formatach; dla każdego typu danych istnieje skrypt w języku Python, który przekształca go w pojedynczy zunifikowany format.

Ten skrypt w języku Python jest powolny i związany z procesorem (z jednym rdzeniem na komputerze wielordzeniowym), dlatego chcę uruchomić trzy jego wystąpienia - po jednym dla każdego typu danych - i połączyć ich dane wyjściowe, aby je przekazać sort. Zasadniczo równoważne z tym:

{ ./handle_1.py; ./handle_2.py; ./handle_3.py } | sort -n

Ale z trzema skryptami działającymi równolegle.

Znalazłem to pytanie, gdzie GNU splitbyło używane do kradzieży okrągłego strumienia stdout między n instancjami skryptu, który obsługuje strumień.

Na podzielonej stronie man:

-n, --number=CHUNKS
          generate CHUNKS output files.  See below
CHUNKS  may be:
 N       split into N files based on size of input
 K/N     output Kth of N to stdout
 l/N     split into N files without splitting lines
 l/K/N   output Kth of N to stdout without splitting lines
 r/N     like 'l'  but  use  round  robin  distributio

Zatem r/Npolecenie implikuje „ bez podziału linii ”.

Na tej podstawie wydaje się, że następujące rozwiązanie powinno być wykonalne:

split -n r/3 -u --filter="./choose_script" << EOF
> 1
> 2
> 3
> EOF

Gdzie choose_scriptto robi:

#!/bin/bash
{ read x; ./handle_$x.py; }

Niestety widzę przenikanie się linii i wiele nowych linii, których nie powinno tam być.

Na przykład, jeśli zastąpię moje skrypty Pythona kilkoma prostymi skryptami bash, które to robią:

#!/bin/bash
# ./handle_1.sh
while true; echo "1-$RANDOM"; done;

.

#!/bin/bash
# ./handle_2.sh
while true; echo "2-$RANDOM"; done;

.

#!/bin/bash
# ./handle_3.sh
while true; echo "3-$RANDOM"; done;

Widzę ten wynik:

1-8394

2-11238
2-22757
1-723
2-6669
3-3690
2-892
2-312511-24152
2-9317
3-5981

Jest to denerwujące - w oparciu o wyciąg ze strony podręcznika, który wkleiłem powyżej, powinien on zachować integralność linii.

Oczywiście działa to, jeśli usunę -uargument, ale następnie jest buforowany i zabraknie pamięci, ponieważ buforuje dane wyjściowe wszystkich skryptów oprócz jednego.

Gdyby ktoś miał tu jakiś wgląd, byłoby to bardzo mile widziane. Jestem tutaj z głębi.

Cera
źródło
Niektórzy ludzie na #bash na freenode zasugerowali, że odrodzę wszystkie trzy procesy i przygotuję je w tle, pisząc do niestandardowych FD, a następnie przeglądam te FD i czytam dla nich wiersze, ale nie zastanawiałem się, jak to zrobić. Powiedziano mi również, aby spojrzeć na coprocwbudowaną wersję bash, chociaż tak naprawdę nie widzę, jak to działa.
Cera,
1
Czy musisz to zrobić bez pośrednich plików? Nie mogłeś po prostu zrobić job1.py > file1 & job2.py > file 2 & job3.py > file3 ; wait ; sort -n file1 file2 file3?
angus

Odpowiedzi:

2

Spróbuj użyć opcji -u GNU równolegle.

echo "1\n2\n3" | parallel -u -IX ./handle_X.sh

To uruchamia je równolegle, bez buforowania całego procesu.

flowblok
źródło
Jestem trochę mylić - jest Xw IXinformując -I, że X będzie flaga zastąpienia, czy też stosując -Xflagę, która pozornie ma istotne znaczenie również?
Cera,
Hmph. Robię to: parallel -u -X ./handle_{}.sh ::: "1" "2" "3"i niestety nadal widzę jakieś zakłócenia w wynikach.
Cera,
pierwsze: możesz także użyć parallel -u ./handle_{}.sh, ale wolę to zmienić, ponieważ nawiasy klamrowe mają również znaczenie łączenia poleceń (jak w twoim pytaniu).
flowblok
Wydaje się pracować dla mnie, mój grep nie odbiera żadnego maglowania: pastie.org/5113187 (? Używasz bash skrypty testu, lub rzeczywiste skrypty Pythona)
flowblok
Problem polega na tym, że tak naprawdę nic nie robi się równolegle. Używam skryptów bash - pastie.org/5113225
Cera,
2

Próbować:

parallel ::: ./handle_1.py ./handle_2.py ./handle_3.py

Jeśli handle_1.pypobiera nazwę pliku:

parallel ::: ./handle_1.py ./handle_2.py ./handle_3.py ::: files*

Nie chcesz mieszać wyników, więc nie używaj -u.

Jeśli chcesz zachować kolejność (więc wszystkie dane wyjściowe handle_1 są przed uchwytem 2, a zatem możesz uniknąć sortowania):

parallel -k  ::: ./handle_1.py ./handle_2.py ./handle_3.py ::: files*

Jeśli nadal chcesz to posortować, możesz równolegle sortować i wykorzystać sort -m:

parallel --files "./handle_{1}.py {2} | sort -n"  ::: 1 2 3 ::: files* | parallel -j1 -X sort -m

Ustaw $ TMPDIR na katalog, który jest wystarczająco duży, aby pomieścić dane wyjściowe.

Ole Tange
źródło
1
Chcę, aby dane wyjściowe były „mieszane” - chcę tylko upewnić się, że każda linia w końcowym wyniku jest pojedynczą linią z jednego z podprocesów. Jeśli go nie zmiksuję, w systemie zabraknie pamięci buforującej standardowe strumienie, które nie są jeszcze drukowane.
Cera,
Dzięki GNU Parallel nie zabraknie ci pamięci: nie buforuje się w pamięci. Jak myślisz, dlaczego buforuje się w pamięci?
Ole Tange
2

Może czegoś mi brakuje, ale nie możesz po prostu zrobić:

(./handle_1.py & ./handle_2.py & ./handle_3.py) | sort -n

Jeśli chcesz, aby wiersze z każdego procesu nie były przeplatane, łatwiej jest prawdopodobnie upewnić się, że sam proces zapisuje je w pełni i ewentualnie wyłącza buforowanie wyjściowe, ponieważ writes do potoku są gwarantowane jako niepodzielne, o ile nie są większe niż PIPE_BUF. Na przykład, można mieć pewność, że nie buforowanie wyjścia użyć la stdioi rozmowy fflushlub cokolwiek odpowiednikiem jest pythonpo jednym lub kilka linii zostały napisane.

Jeśli nie możesz zmodyfikować skryptów Pythona, możesz:

lb() { grep --line-buffered '^'; }

(z GNU grep) lub:

lb() while IFS= read -r l; do printf '%s\n' "$l"; done

(Patrz uwagi w komentarzach poniżej, jeśli dane wyjściowe poleceń nie są tekstem)

I robić:

(./handle_1.py | lb & ./handle_2.py | lb & ./handle_3.py | lb) | sort -n

Inną opcją uniknięcia tych 3 lbprocesów jest posiadanie trzech potoków do jednego polecenia, które używa select/, pollaby zobaczyć, skąd pochodzi dane wyjściowe i podaje je do sortlinii, ale wymaga to trochę programowania.

Stéphane Chazelas
źródło
waitMyślę, że potrzebujesz tam.
derobert
1
Nie, chyba że niektóre programy zamkną swoje standardowe wyjście przed wyjściem, ponieważ potok i sort -npozostanie, dopóki wszystkie programy, które mają fd otwarte, nie wyjdą.
Stéphane Chazelas
Rzeczywiście, testowałem, masz rację.
derobert
Nie, nadal otrzymuję zniekształcone wyjście. Linie mieszają się i przeplatają.
Cera,
1
OK @Cerales, zobacz moją zaktualizowaną odpowiedź
Stéphane Chazelas,
1

Odpowiedź Flowbok była poprawnym rozwiązaniem. Co dziwne, wyjście GNU parallelzostaje zniekształcone, jeśli jest wyprowadzane bezpośrednio do pliku - ale nie, jeśli idzie do tty.

Na szczęście script -cjest dostępny do naśladowania tty.

Nadal są trzy skrypty:

#!/bin/bash
# handle_1.sh
while true; do echo "1-$RANDOM$RANDOM$RANDOM$RANDOM"; done

.

#!/bin/bash
# handle_2.sh
while true; do echo "2-$RANDOM$RANDOM$RANDOM$RANDOM"; done

.

#!/bin/bash
# handle_3.sh
while true; do echo "3-$RANDOM$RANDOM$RANDOM$RANDOM"; done

Następnie jest plik, który zawiera wywołanie połączenia równoległego:

#!/bin/bash
# run_parallel.sh
parallel -u -I N ./handle_N.sh ::: "1" "2" "3"

A potem nazywam to tak:

script -c ./run_parallel > output

Linie wyjściowe są mieszane linia po linii między wynikami różnych skryptów, ale nie są zniekształcane ani przeplatane w danym wierszu.

Dziwne zachowanie z parallel- mogę złożyć raport o błędzie.

Cera
źródło