Création d'un seul flux de sortie à partir de trois autres flux produits en parallèle

10

J'ai trois types de données qui sont dans différents formats; pour chaque type de données, il existe un script Python qui le transforme en un seul format unifié.

Ce script Python est lent et lié au CPU (à un seul cœur sur une machine multi-cœur), donc je veux en exécuter trois instances - une pour chaque type de données - et combiner leur sortie pour la transmettre sort. Fondamentalement, équivalent à ceci:

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

Mais avec les trois scripts fonctionnant en parallèle.

J'ai trouvé cette question où GNU splitétait utilisé pour effectuer un round-robin sur un flux standard entre n instances d'un script qui gère le flux.

À partir de la page de manuel fractionnée:

-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

La r/Ncommande implique donc " sans séparer les lignes ".

Sur cette base, il semble que la solution suivante devrait être réalisable:

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

choose_scriptest-ce:

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

Malheureusement, je vois un mélange de lignes - et beaucoup de nouvelles lignes qui ne devraient pas être là.

Par exemple, si je remplace mes scripts Python par des scripts bash simples qui font ceci:

#!/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;

Je vois cette sortie:

1-8394

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

C'est ennuyeux - sur la base de l'extrait de page de manuel que j'ai collé ci-dessus, il devrait maintenir l'intégrité de la ligne.

Évidemment, cela fonctionne si je supprime l' -uargument, mais il est mis en mémoire tampon et je manquerai de mémoire car il met en mémoire tampon la sortie de tous les scripts sauf un.

Si quelqu'un a un aperçu ici, ce serait grandement apprécié. Je suis hors de ma profondeur ici.

Cera
la source
Certaines personnes dans #bash sur freenode ont suggéré que je génère les trois processus et les mets en arrière-plan, en écrivant sur des FD personnalisés, puis en boucle sur ces FD et en lisant des lignes pour eux, mais je n'ai pas compris comment rendre cela réalisable. On m'a également dit de regarder le coprocmodule intégré dans bash, bien que je ne vois pas vraiment comment cela s'applique.
Cera
1
Devez-vous le faire sans fichiers intermédiaires? Ne pourriez-vous pas simplement faire job1.py > file1 & job2.py > file 2 & job3.py > file3 ; wait ; sort -n file1 file2 file3?
angus

Réponses:

2

Essayez d'utiliser l'option -u de GNU parallel.

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

Cela les exécute en parallèle, sans mettre en mémoire tampon l'intégralité d'un processus.

flowblok
la source
Je suis un peu confus - est-ce Xà IXdire -Ique X sera le drapeau pour remplacer, ou applique-t-il le -Xdrapeau, qui a apparemment aussi une signification pertinente?
Cera
Hmph. Je fais cela parallel -u -X ./handle_{}.sh ::: "1" "2" "3":, et malheureusement, je vois toujours des changements de sortie.
Cera
l'ancien: vous pouvez également utiliser parallel -u ./handle_{}.sh, mais je préfère le changer, car les accolades ont également le sens de joindre les commandes (comme dans votre question).
flowblok
Semble fonctionner pour moi, mon grep ne détecte aucune altération: pastie.org/5113187 (utilisez-vous les scripts de test bash ou vos scripts Python réels?)
flowblok
Le problème est que cela ne fait rien en parallèle. J'utilise les scripts bash - pastie.org/5113225
Cera
2

Essayer:

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

Si handle_1.pyprend un nom de fichier:

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

Vous ne voulez pas que la sortie soit mélangée, alors n'utilisez pas -u.

Si vous souhaitez conserver l'ordre (donc toutes les sorties de handle_1 sont antérieures à handle_2 et vous pourrez ainsi éviter le tri):

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

Si vous voulez toujours le trier, vous pouvez paralléliser le tri et utiliser sort -m:

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

Définissez $ TMPDIR sur un répertoire suffisamment grand pour contenir la sortie.

Ole Tange
la source
1
Je veux que la sortie soit «mélangée» - je veux juste m'assurer que chaque ligne de la sortie finale est une seule ligne de l'un des sous-processus. Si je ne le mélange pas, le système manquera de mémoire tampon les flux stdout qui ne sont pas encore imprimés.
Cera
Avec GNU Parallel, vous ne manquerez pas de mémoire: il ne met pas en mémoire tampon. Pourquoi pensez-vous qu'il met en mémoire tampon?
Ole Tange
2

Peut-être que je manque quelque chose, mais ne pouvez-vous pas simplement faire:

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

Si vous souhaitez que les lignes de chaque processus ne soient pas entrelacées, le plus simple est probablement de vous assurer que le processus lui-même les écrit complètement et éventuellement de désactiver la mise en mémoire tampon de sortie car writes dans un tuyau sont garantis atomiques tant qu'elles ne sont pas plus grandes que PIPE_BUF. Par exemple, vous pouvez vous assurer qu'il ne utilisation en mémoire tampon de sortie à la stdioet appel fflushou quel que soit l'équivalent est pythonaprès une ou quelques lignes ont été écrites.

Si vous ne pouvez pas modifier les scripts python, vous pouvez faire:

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

(avec GNU grep) ou:

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

(Voir les notes dans les commentaires ci-dessous si ce que la sortie des commandes n'est pas du texte)

Et fait:

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

Une autre option pour éviter ces 3 lbprocessus est d'avoir trois canaux vers une commande qui utilise select/ pollpour voir d'où vient une sortie et la nourrir en sortligne, mais cela prend un peu de programmation.

Stéphane Chazelas
la source
Vous en avez besoin wait, je pense.
derobert
1
Non, sauf si certains programmes ferment leur sortie standard avant de quitter, car le canal et sort -nrestera jusqu'à ce que tous les programmes ayant un fd ouvert se soient fermés.
Stéphane Chazelas
En effet, j'ai testé, tu as raison.
derobert
Non, je reçois toujours une sortie mutilée. Les lignes sont mélangées et entrelacées.
Cera
1
OK @Cerales, voir ma réponse mise à jour
Stéphane Chazelas
1

La réponse de Flowbok était la bonne solution. Curieusement, la sortie de GNU parallelest altérée si elle est directement envoyée vers un fichier - mais pas si elle va vers un tty.

Heureusement, script -cest disponible pour imiter un tty.

Il y a encore les trois scripts:

#!/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

Ensuite, il y a un fichier qui encapsule l'appel à parallèle:

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

Et puis je l'appelle comme ça:

script -c ./run_parallel > output

Les lignes en sortie sont mélangées ligne par ligne entre la sortie des différents scripts, mais elles ne sont pas altérées ou entrelacées sur une ligne donnée.

Comportement bizarre de parallel- je peux déposer un rapport de bogue.

Cera
la source