Exécution de commandes canalisées en parallèle

16

Considérez le scénario suivant. J'ai deux programmes A et B. Le programme A sort des lignes de chaînes standard tandis que le programme B traite les lignes de stdin. La façon d'utiliser ces deux programmes est bien sûr:

foo @ bar: ~ $ A | B

Maintenant, j'ai remarqué que cela ne mange qu'un seul noyau; je me demande donc:

Les programmes A et B partagent-ils les mêmes ressources de calcul? Si oui, existe-t-il un moyen d'exécuter A et B simultanément?

Une autre chose que j'ai remarquée est que A s'exécute beaucoup plus rapidement que B, donc je me demande si pourrait en quelque sorte exécuter plus de programmes B et les laisser traiter les lignes que A sort en parallèle.

Autrement dit, A afficherait ses lignes, et il y aurait N instances de programmes B qui liraient ces lignes (celui qui les lirait en premier) les traiteraient et les sortiraient sur stdout.

Donc ma dernière question est:

Existe-t-il un moyen de diriger la sortie vers A parmi plusieurs processus B sans avoir à prendre en compte les conditions de concurrence et les autres incohérences qui pourraient survenir?

Jernej
la source
1
Bien qu'il A | B | Csoit parallèle comme dans des processus séparés, en raison de la nature des tuyaux (B doit attendre la sortie de A, C doit attendre la sortie de B), il peut toujours être linéaire dans certains cas. Cela dépend entièrement du type de sortie qu'ils produisent. Il n'y a pas beaucoup de cas où l'exécution multiple Baiderait beaucoup, il est tout à fait possible que l'exemple de wc parallèle soit plus lent que normal wccar le fractionnement peut prendre plus de ressources que de compter les lignes normalement. Utilisez avec précaution.
frostschutz

Réponses:

14

Un problème split --filterest que la sortie peut être mélangée, donc vous obtenez une demi-ligne du processus 1 suivie d'une demi-ligne du processus 2.

GNU Parallel garantit qu'il n'y aura pas de confusion.

Supposez donc que vous voulez faire:

 A | B | C

Mais ce B est terriblement lent, et donc vous voulez paralléliser cela. Ensuite, vous pouvez faire:

A | parallel --pipe B | C

GNU Parallel se divise par défaut sur \ n et une taille de bloc de 1 Mo. Cela peut être ajusté avec --recend et --block.

Vous pouvez trouver plus d'informations sur GNU Parallel sur: http://www.gnu.org/s/parallel/

Vous pouvez installer GNU Parallel en seulement 10 secondes avec:

wget -O - pi.dk/3 | sh 

Regardez la vidéo d'introduction sur http://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

Ole Tange
la source
1
Alors que je suis fortement en désaccord sur la méthode d'installation :-), +1 car votre solution résout la plupart des problèmes avec la mienne.
LSerni
Celui-ci est vraiment sympa. Avez-vous également des suggestions pour les paramètres à utiliser? Je sais que le programme A produira plus de 1 To de données environ 5 Go par minute. Le programme B traite les données 5 fois plus lentement que les sorties A et j'ai 5 cœurs à ma disposition pour cette tâche.
Jernej
GNU Parallel peut actuellement gérer au plus environ 100 Mo / s, vous allez donc toucher cette limite. L'optimale --block-sizedépendra de la quantité de RAM et de la vitesse à laquelle vous pourrez en démarrer une nouvelle B. Dans votre situation, j'utiliserais --block 100Met verrais comment cela a fonctionné.
Ole Tange
@lserni Pouvez-vous trouver une méthode d'installation qui soit meilleure, qui fonctionne sur la plupart des machines UNIX et nécessite une quantité de travail similaire de la part de l'utilisateur?
Ole Tange
4
Désolé, je n'ai pas été clair. La méthode d'installation - le script transmis à sh- est excellente. Le problème consiste à le passer à sh: télécharger et exécuter du code exécutable à partir d'un site . Attention, je suis peut-être trop paranoïaque, car on pourrait objecter qu'un RPM ou un DEB sur mesure est fondamentalement la même chose, et même publier le code sur une page à copier et coller entraînerait des personnes aveugles en tous cas.
LSerni
13

Lorsque vous écrivez A | B, les deux processus s'exécutent déjà en parallèle. Si vous les voyez comme n'utilisant qu'un seul cœur, c'est probablement à cause des paramètres d'affinité du processeur (peut-être qu'il existe un outil pour générer un processus avec une affinité différente) ou parce qu'un processus ne suffit pas pour contenir un cœur entier et le système " préfère "ne pas étendre l'informatique.

Pour exécuter plusieurs B avec un A, vous avez besoin d'un outil tel que splitl' --filteroption:

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

Cependant, cela risque de perturber l'ordre des lignes dans la sortie, car les travaux B ne s'exécuteront pas tous à la même vitesse. S'il s'agit d'un problème, vous devrez peut-être rediriger la sortie B i-ème vers un fichier intermédiaire et les assembler à la fin à l'aide de cat. Ceci, à son tour, peut nécessiter un espace disque considérable.

D' autres options existent (par exemple , vous pouvez limiter chaque instance de B à une seule sortie en mémoire tampon ligne, attendez jusqu'à ce qu'un tout « rond » de B est terminée, lancez l'équivalent d'une réduire à split« s carte , et catla sortie temporaire ensemble), avec différents niveaux d'efficacité. L'option 'round' qui vient d'être décrite, par exemple, attendra la fin de l' instance la plus lente de B , elle dépendra donc grandement de la mise en mémoire tampon disponible pour B; [m]bufferpourrait aider, ou non, selon les opérations.

Exemples

Générez les 1000 premiers nombres et comptez les lignes en parallèle:

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

Si nous devions "marquer" les lignes, nous verrions que chaque première ligne est envoyée au processus # 1, chaque cinquième ligne au processus # 5 et ainsi de suite. De plus, dans le temps qu'il faut splitpour engendrer le second processus, le premier est déjà un bon chemin dans son quota:

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

Lors de l'exécution sur une machine à 2 cœurs seq, splitet les wcprocessus partagent les cœurs; mais en y regardant de plus près, le système laisse les deux premiers processus sur CPU0 et divise CPU1 entre les processus de travail:

%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

Notez surtout que splitconsomme une quantité considérable de CPU. Cela diminuera proportionnellement aux besoins de A; c'est-à-dire que si A est un processus plus lourd que seq, la surcharge relative de splitdiminuera. Mais si A est un processus très léger et B est assez rapide (de sorte que vous n'avez pas besoin de plus de 2-3 B pour rester avec A), la parallélisation avec split(ou les tuyaux en général) pourrait ne pas en valoir la peine.

LSerni
la source
Il est intéressant de noter que la division trouvée sur Ubuntu n'a pas l'option --filter. Quel type de système d'exploitation utilisez-vous pour cela?
Jernej
Linux OpenSuSE 12.3, avec coreutils ( gnu.org/software/coreutils/manual/html_node/… ). Je vais essayer de me procurer un Ubuntu, ils ont peut-être changé le nom pour accueillir un outil du même nom.
LSerni
Êtes-vous sûr de l' split --filteroption manquante? Sur mon Ubuntu 12.04-LTS ("wheezy / sid"), il est là, et mes exemples fonctionnent. Auriez-vous pu en installer un autre splitque celui de GNU coreutils?
LSerni
Merci pour cela. J'ai dû installer une version plus récente de Coreutils. BTW, j'ai remarqué que si j'exécute le programme A seul, il mange un noyau entier (100%) si j'exécute A | B puis ils mangent ensemble un noyau entier, traitent A en mangeant 15% et traitent B en mangeant 85%.
Jernej
2
Cela est probablement dû au blocage . Si B est plus lourd que A, alors A ne peut pas envoyer sa sortie et est ralenti. Une autre possibilité est que A cède la place à B pendant son fonctionnement (par exemple, disque / net). Sur un autre système, vous pourriez voir B engloutir 100% de CPU1 et A se voir attribuer 18% de CPU0. Vous avez probablement besoin de 85/15 ~ 5,67 = entre 5 et 6 instances de B pour obtenir une seule instance A pour saturer un seul cœur. Les E / S, si elles sont présentes, peuvent cependant fausser ces valeurs.
LSerni