web-dev-qa-db-fra.com

Comment les pipelines limitent-ils l'utilisation de la mémoire?

Brian Kernighan explique dans cette vidéo l'attrait des premiers Bell Labs pour les petits langages/programmes étant basé sur des limitations de mémoire

Une grosse machine aurait 64 koctets - K, pas M ou G - et donc cela signifiait qu'un programme individuel ne pouvait pas être très gros, et donc il y avait une tendance naturelle à écrire de petits programmes, puis le mécanisme de pipe, essentiellement la redirection de sortie d'entrée, a permis de lier un programme à un autre.

Mais je ne comprends pas comment cela pourrait limiter l'utilisation de la mémoire compte tenu du fait que les données doivent être stockées dans RAM pour transmettre entre les programmes.

De Wikipedia :

Dans la plupart des systèmes de type Unix, tous les processus d'un pipeline sont démarrés en même temps [emphase sur moi] , avec leurs flux correctement connectés et gérés par le planificateur avec tous les autres processus en cours d'exécution sur la machine. Un aspect important de cela, en distinguant les tuyaux Unix des autres implémentations de tuyaux, est le concept de mise en mémoire tampon: par exemple, un programme d'envoi peut produire 5000 octets par seconde, et un programme de réception ne peut accepter que 100 octets par seconde, mais pas les données sont perdues. Au lieu de cela, la sortie du programme d'envoi est conservée dans le tampon. Lorsque le programme récepteur est prêt à lire les données, le programme suivant dans le pipeline lit à partir du tampon. Sous Linux, la taille du tampon est de 65536 octets (64 Ko). Un filtre tiers open source appelé bfr est disponible pour fournir des tampons plus importants si nécessaire.

Cela m'embrouille encore plus, car cela va complètement à l'encontre de l'objectif des petits programmes (bien qu'ils soient modulaires jusqu'à une certaine échelle).

La seule chose à laquelle je peux penser comme une solution à ma première question (les limites de la mémoire étant problématiques en fonction des données de taille) serait que les grands ensembles de données n'étaient tout simplement pas calculés à l'époque et que les vrais problèmes à résoudre étaient censés résoudre. quantité de mémoire requise par les programmes eux-mêmes. Mais étant donné le texte en gras dans la citation de Wikipédia, même cela me confond: comme un programme n'est pas mis en œuvre à la fois.

Tout cela aurait beaucoup de sens si des fichiers temporaires étaient utilisés, mais je crois comprendre que les canaux n'écrivent pas sur le disque (sauf si le swap est utilisé).

Exemple:

sed 'simplesubstitution' file | sort | uniq > file2

Il est clair pour moi que sed lit le fichier et le recrache ligne par ligne. Mais sort, comme l'indique BK dans la vidéo liée, est un arrêt complet, donc toutes les données doivent être lues en mémoire (ou le fait-il?), Puis elles sont transmises à uniq, qui (à mon avis) serait un programme d'une ligne à la fois. Mais entre le premier et le deuxième canal, toutes les données doivent être en mémoire, non?

36
malan

Les données n'ont pas besoin d'être stockées dans la RAM. Les pipes bloquent leurs écrivains si les lecteurs ne sont pas là ou ne peuvent pas suivre; sous Linux (et la plupart des autres implémentations, j'imagine), il y a un tampon mais ce n'est pas nécessaire. Comme mentionné par mtraceur et JdeBP (voir la réponse de ce dernier ), les premières versions des canaux tamponnés Unix sur le disque, et c'est ainsi qu'ils ont aidé à limiter utilisation de la mémoire: un pipeline de traitement pourrait être divisé en petits programmes, chacun traitant certaines données, dans les limites des tampons de disque. Les petits programmes prennent moins de mémoire et l'utilisation de canaux signifiait que le traitement pouvait être sérialisé: le premier programme s'exécutait, remplissait son tampon de sortie, était suspendu, puis le deuxième programme était programmé, traitait le tampon, etc. Les systèmes modernes sont des commandes de magnitude plus grande que les premiers systèmes Unix, et peut exécuter de nombreux tuyaux en parallèle; mais pour des quantités énormes de données, vous constaterez toujours un effet similaire (et des variantes de ce type de technique sont utilisées pour le traitement des "mégadonnées").

Dans votre exemple,

sed 'simplesubstitution' file | sort | uniq > file2

sed lit les données de file si nécessaire, puis les écrit aussi longtemps que sort est prêt à les lire; si sort n'est pas prêt, les blocs d'écriture. Les données vivent en effet finalement en mémoire, mais cela est spécifique à sort, et sort est prêt à faire face à tous les problèmes (il utilisera des fichiers temporaires si la quantité de données à trier est trop grande ).

Vous pouvez voir le comportement de blocage en exécutant

strace seq 1000000 -1 1 | (sleep 120; sort -n)

Cela produit une bonne quantité de données et les redirige vers un processus qui n'est pas prêt à lire quoi que ce soit pendant les deux premières minutes. Vous verrez un certain nombre d'opérations write passer, mais très rapidement seq s'arrêtera et attendra que les deux minutes se soient écoulées, bloquées par le noyau (le système write appels en attente).

45
Stephen Kitt

Mais je ne comprends pas comment cela pourrait limiter l'utilisation de la mémoire compte tenu du fait que les données doivent être stockées dans RAM pour transmettre entre les programmes.

Ceci est votre erreur fondamentale. Les premières versions d'Unix ne contenaient pas de données de canal dans la RAM. Ils les ont stockés sur disque. Les tuyaux avaient des nœuds i; sur un périphérique de disque qui a été noté périphérique de tuyau . L'administrateur système a exécuté un programme nommé /etc/config pour spécifier (entre autres) quel volume sur quel disque était le périphérique de pipe, quel volume était le périphérique racine , et lequel le périphérique de vidage .

La quantité de données en attente a été limitée par le fait que seuls les blocs directs du i-node sur le disque ont été utilisés pour le stockage. Ce mécanisme a simplifié le code, car le même algorithme a été utilisé pour la lecture à partir d'un canal que pour la lecture d'un fichier normal, avec quelques ajustements dus au fait que les canaux ne sont pas recherchables et que le tampon est circulaire.

Ce mécanisme a été remplacé par d'autres du milieu à la fin des années 80. SCO XENIX a gagné le "High Performance Pipe System", qui a remplacé les i-noeuds par des tampons internes. 4BSD a transformé les tubes sans nom en paires de sockets. AT&T a réimplémenté les tubes en utilisant le mécanisme STREAMS.

Et bien sûr, le programme sort a effectué un tri interne limité de blocs d'entrée de 32 Ko (ou toute autre quantité de mémoire plus petite qu'il pourrait allouer si 32 Ko n'était pas disponible), en écrivant les résultats triés sur l'intermédiaire stmX?? fichiers dans /usr/tmp/ qu'il fusionne ensuite en externe trié pour fournir la sortie finale.

Lectures complémentaires

  • Steve D. Pate (1996). "Communication interprocessus". Internes UNIX: une approche pratique . Addison-Wesley. ISBN 9780201877212.
  • Maurice J. Bach (1987). "Appels système pour le système de fichiers". La conception du système d'exploitation Unix . Prentice Hall. ISBN 0132017571.
  • Steven V. Earhart (1986). "config (1M)". Manuel du programmeur Unix: 3. Fonctions d'administration système . Holt, Rinehart et Winston. ISBN 0030093139. p. 23-28.
  • Abhijit Menon-Sen (2020-03-23). Comment les canaux Unix sont-ils implémentés?. toroid.org.
35
JdeBP

Vous avez partiellement raison, mais seulement par accident.

Dans votre exemple, toutes les données doivent en effet avoir été lues "entre" les canaux, mais elles ne doivent pas nécessairement résider en mémoire (y compris la mémoire virtuelle). Les implémentations habituelles de sort peuvent trier des ensembles de données qui ne rentrent pas dans RAM en effectuant des tri partiels dans les fichiers temporaires et en les fusionnant. Cependant, il est un fait donné que vous ne pouvez pas éventuellement sortir une séquence triée avant d'avoir lu chaque élément. C'est assez évident. Donc oui, sort ne peut commencer la sortie vers le deuxième canal qu'après avoir lu (et fait quoi que ce soit, en triant partiellement les fichiers temporaires) tout depuis le premier. Mais cela pas doit nécessairement tout garder dans la RAM.

Cependant, cela n'a rien à voir avec le fonctionnement des tuyaux. Les tuyaux peuvent être nommés (ils étaient traditionnellement tous nommés), ce qui signifie rien de plus et rien de moins qu'ils ont un emplacement dans le système de fichiers, comme les fichiers. Et c'est exactement ce que les tuyaux étaient autrefois, les fichiers (avec des écritures fusionnées autant que la disponibilité de la mémoire physique le permettrait, comme une optimisation).

De nos jours, les tuyaux sont un petit tampon de noyau de taille finie dans lequel les données sont copiées, du moins c'est ce qui se passe conceptuellement. Si le noyau peut l'aider, les copies sont élidées en jouant VM astuces (par exemple, le piping à partir d'un fichier rend généralement la même page disponible pour que l'autre processus puisse le lire, donc ce n'est finalement qu'un opération de lecture, pas deux copies, et aucune mémoire supplémentaire que celle déjà utilisée par le cache tampon n'est nécessaire de toute façon. Dans certaines situations, vous pouvez également obtenir une copie à 100% nulle. Ou quelque chose de très proche.

Si les tuyaux sont petits et de taille finie, comment cela peut-il fonctionner pour une quantité de données inconnue (éventuellement importante)? C'est simple: quand rien ne rentre plus, l'écriture se bloque jusqu'à ce qu'il y ait de la place.

La philosophie de nombreux programmes simples était plus utile à une époque où la mémoire était très rare. Parce que, eh bien, vous pouvez travailler par petites étapes, une à la fois. De nos jours, les avantages sont, à part une certaine flexibilité supplémentaire, je suppose, pas plus grands.
Cependant, les tuyaux sont mis en œuvre très efficacement (ils devaient l'être!), Donc il n'y a pas d'inconvénient non plus, et c'est une chose établie qui fonctionne bien et à laquelle les gens sont habitués, donc il n'y a pas besoin de changer le paradigme.

1
Damon