&Notepad

Will Crichton – 9 septembre 2018
J’ai un problème avec l’expression « programmation de systèmes ». Pour moi, elle a toujours semblé combiner inutilement deux idées : la programmation de bas niveau (traiter les détails de mise en œuvre de la machine) et la conception de systèmes (créer et gérer un ensemble complexe de composants interopérables). Pourquoi en est-il ainsi ? Depuis combien de temps cela est-il vrai ? Et que pourrions-nous gagner à redéfinir l’idée de systèmes ?

années 1970 : Améliorer l’assemblage

Voyons les origines des systèmes informatiques modernes pour comprendre comment le terme a évolué. Je ne sais pas qui a inventé l’expression à l’origine, mais mes recherches suggèrent que les efforts sérieux pour définir les « systèmes informatiques » ont commencé vers le début des années 70. Dans Systems Programming Languages (Bergeron1 et al. 1972), les auteurs disent:

Un programme système est un ensemble intégré de sous-programmes,formant ensemble un tout plus grand que la somme de ses parties, et dépassant un certain seuil de taille et/ou de complexité. Des exemples typiques sont les systèmes de multiprogrammation, de traduction, de simulation, de gestion de l’information et de partage du temps. Ce qui suit est un ensemble partiel de propriétés,dont certaines se retrouvent dans des non-systèmes, et dont toutes n’ont pas besoin d’être présentes dans un système donné.

  1. Le problème à résoudre est d’une nature large, composé de nombreux sous-problèmes, généralement très variés.
  2. Le programme système est susceptible d’être utilisé pour supporter d’autres logiciels et programmes d’applications, mais il peut aussi être un paquet d’applications complet lui-même.
  3. Il est conçu pour une utilisation continue de « production » plutôt que pour une solution unique à un seul problème d’applications.
  4. Il est susceptible d’évoluer continuellement dans le nombre et les types de fonctionnalités qu’il prend en charge.
  5. Un programme de système exige une certaine discipline ou structure, à la fois au sein des modules et entre eux (c’est-à-dire la « communication »), et est généralement conçu et mis en œuvre par plus d’une personne.

Cette définition est assez acceptable – les systèmes informatiques sont à grande échelle, utilisés depuis longtemps et varient dans le temps. Cependant, alors que cette définition est largement descriptive, une idée clé de l’article est prescriptive : plaider pour la séparation des langages de bas niveau des langages de systèmes (à l’époque, opposer l’assembleur au FORTRAN).

Le but d’un langage de programmation de systèmes est de fournir un langage qui peut être utilisé sans préoccupation excessive pour les considérations de « bit twiddling »,tout en générant un code qui n’est pas sensiblement pire que celui généré à la main. Un tel langage devrait combiner la concision et la lisibilité des langages de haut niveau avec l’efficacité en termes d’espace et de temps et la capacité d’accéder aux fonctions de la machine et du système d’exploitation que l’on peut obtenir en langage assembleur. Le temps de conception, d’écriture et de débogage devrait être réduit au minimum sans imposer de surcharge inutile aux ressources des systèmes.

Au même moment, des chercheurs de CMU publiaient BLISS : A Language for Systems Programming (Wulf et al. 1972), le décrivant comme:

Nous nous référons à BLISS comme à un « langage d’implémentation », bien que nous admettions que le terme est quelque peu ambigu puisque, vraisemblablement, tous les langages informatiques sont utilisés pour implémenter quelque chose. Pour nous, cette expression désigne un langage de haut niveau à usage général dans lequel l’accent a été mis sur une application spécifique, à savoir l’écriture de grands systèmes logiciels de production pour une machine spécifique. Les langages à usage spécifique, tels que les compilateurs-compilateurs, n’entrent pas dans cette catégorie, et nous ne supposons pas nécessairement que ces langages doivent être indépendants de la machine. Nous insistons sur le mot « implémentation » dans notre définition et n’avons pas utilisé de mots tels que « conception » et « documentation ». Nous ne nous attendons pas nécessairement à ce qu’un langage de mise en œuvre soit un véhicule approprié pour exprimer la conception initiale d’un grand système ni pour la documentation exclusive de ce système. Des concepts tels que l’indépendance de la machine, l’expression de la conception et de l’implémentation dans la même notation, l’auto-documentation, et d’autres, sont clairement des objectifs souhaitables et sont des critères par lesquels nous avons évalué divers langages.

Ici, les auteurs opposent l’idée d’un « langage d’implémentation » comme étant de plus haut niveau que l’assemblage, mais de plus bas niveau qu’un « langage de conception ». Cela résiste à la définition de l’article précédent, préconisant que la conception d’un système et l’implémentation d’un système devraient avoir des langages séparés.

Ces deux articles sont des artefacts de recherche ou des plaidoyers. La dernière entrée à considérer (également de 1972, une année productive !) est Systems Programming (Donovan 1972), un texte éducatif pour apprendre la programmation des systèmes.

Qu’est-ce que la programmation des systèmes ? Vous pouvez visualiser un ordinateur comme une sorte de bête qui obéit à toutes les commandes. On a dit que les ordinateurs sont essentiellement des personnes faites de métal ou, inversement, les personnes sont des ordinateurs faits de chair et de sang. Cependant, dès que nous nous approchons des ordinateurs, nous constatons qu’il s’agit essentiellement de machines qui suivent des instructions très spécifiques et primitives. Aux premiers jours des ordinateurs, les gens communiquaient avec eux par le biais d’interrupteurs de mise en marche et d’arrêt correspondant à des instructions primitives. Très vite, les gens ont voulu donner des instructions plus complexes. Par exemple, ils voulaient pouvoir dire X = 30 * Y ; étant donné que Y = 10, quelle est la valeur de X ? Les ordinateurs actuels ne peuvent pas comprendre un tel langage sans l’aide de programmes systèmes. Les programmes systèmes (par exemple, les compilateurs, les chargeurs, les macro-processeurs, les systèmes d’exploitation) ont été développés pour que les ordinateurs soient mieux adaptés aux besoins de leurs utilisateurs. De plus, les gens voulaient plus d’assistance dans la mécanique de préparation de leurs programmes.

J’aime que cette définition nous rappelle que les systèmes sont au service des gens, même s’ils ne sont qu’une infrastructure non exposée directement à l’utilisateur final.

années 1990 : L’essor du scripting

Dans les années 70 et 80, il semble que la plupart des chercheurs voyaient la programmation de systèmes habituellement comme un contraste avec la programmation d’assemblage. Il n’y avait tout simplement pas d’autres bons outils pour construire des systèmes. (Je ne suis pas sûr de la place de Lisp dans tout cela ? Aucune des ressources que j’ai lues ne citait Lisp, bien que je sois vaguement conscient que les machines Lisp ont existé, même brièvement.)

Cependant, au milieu des années 90, un changement de mer majeur s’est produit dans les langages de programmation avec la montée des langages de script à typage dynamique. Améliorant les systèmes de scripts shell antérieurs comme Bash, des langages comme Perl (1987), Tcl (1988), Python (1990), Ruby (1995), PHP (1995) et Javascript (1995) se sont imposés dans le courant dominant. Cette évolution a abouti à l’article influent « Scripting : Higher Level Programming for the 21st Century » (Ousterhout 1998). Celui-ci articulait « la dichotomie d’Ousterhout » entre les « langages de programmation système » et les « langages de scripting ».

Les langages de scripting sont conçus pour des tâches différentes de celles des langages de programmation système, ce qui entraîne des différences fondamentales dans les langages. Les langages de programmation système ont été conçus pour construire des structures de données et des algorithmes à partir de rien, en partant des éléments informatiques les plus primitifs comme les mots de mémoire. En revanche, les langages de script sont conçus pour coller : ils supposent l’existence d’un ensemble de composants puissants et sont principalement destinés à relier les composants entre eux. Les langages de programmation système sont fortement typés pour aider à gérer la complexité, tandis que les langages de script sont sans typage pour simplifier les connexions entre les composants et permettre un développement rapide des applications. Plusieurs tendances récentes, telles que des machines plus rapides, de meilleurs langages de script, l’importance croissante des interfaces utilisateur graphiques et des architectures de composants, ainsi que la croissance d’Internet, ont considérablement augmenté l’applicabilité des langages de script.

Sur le plan technique, Ousterhout a opposé le scripting aux systèmes selon les axes de la sécurité de type et des instructions par déclaration, comme indiqué ci-dessus. Au niveau de la conception, il a caractérisé les nouveaux rôles de chaque classe de langage : la programmation de systèmes sert à créer des composants, et le scripting à les coller ensemble.

A peu près à la même époque, les langages typés statiquement mais collectés en mémoire ont également commencé à gagner en popularité. Java (1995) et C# (2000) sont devenus les titans que nous connaissons aujourd’hui. Bien que ces deux langages ne soient pas traditionnellement considérés comme des « langages de programmation de systèmes », ils ont été utilisés pour concevoir un grand nombre des plus grands systèmes logiciels du monde. Ousterhout a même mentionné explicitement « dans le monde de l’Internet qui prend forme maintenant, Java est utilisé pour la programmation système. »

2010s : Les frontières s’estompent

Au cours de la dernière décennie, la frontière entre les langages de script et les langages de programmation système a commencé à s’estomper. Des entreprises comme Dropbox ont pu construire des systèmes étonnamment grands et évolutifs avec seulement Python. Javascript est utilisé pour rendre en temps réel des interfaces utilisateur complexes dans des milliards de pages web. Le typage progressif a gagné en puissance dans Python, Javascript et d’autres langages de script, permettant une transition du code « prototype » au code « production » en ajoutant de manière incrémentielle des informations de type statique.

Dans le même temps, les ressources massives d’ingénierie versées dans les compilateurs JIT pour les langages statiques (par exemple HotSpot de Java) et les langages dynamiques (par exemple LuaJIT de Lua, V8 de Javascript, PyPy de Python) ont rendu leurs performances compétitives par rapport aux langages de programmation de systèmes traditionnels (C, C++). Les systèmes distribués à grande échelle comme Spark sont écrits en Scala2. De nouveaux langages de programmation comme Julia, Swift et Go continuent de repousser les limites de performance des langages à ramassage d’ordures.

Un panel intitulé Programmation de systèmes en 2014 et au-delà a présenté les plus grands esprits derrière les langages de systèmes auto-identifiés d’aujourd’hui : Bjarne Stroustrup (créateur de C++), Rob Pike (créateur de Go), Andrei Alexandrescu (développeur de D) et Niko Matsakis (développeur de Rust). À la question  » qu’est-ce qu’un langage de programmation système en 2014 ? « , ils ont répondu (transcription éditée) :

  • Niko Matsakis : L’écriture d’applications côté client. L’opposé polaire de ce pour quoi Go est conçu. Dans ces applications, vous avez des besoins de latence élevés, des exigences de sécurité élevées, beaucoup d’exigences qui ne se présentent pas du côté serveur.
  • Bjarne Stroustrup : La programmation de systèmes est venue du domaine où vous deviez vous occuper du matériel, et ensuite les applications sont devenues plus compliquées. Vous devez gérer la complexité. Si vous avez des problèmes de contraintes de ressources importantes, vous êtes dans le domaine de la programmation de systèmes. Si vous avez besoin d’un contrôle plus fin, vous êtes également dans le domaine de la programmation des systèmes. Ce sont les contraintes qui déterminent s’il s’agit de programmation de systèmes. Est-ce que vous manquez de mémoire ? Êtes-vous à court de temps ?
  • Rob Pike : Quand nous avons annoncé Go pour la première fois, nous l’avons appelé un langage de programmation système, et je le regrette légèrement parce que beaucoup de gens ont supposé que c’était un langage d’écriture de systèmes d’exploitation. Ce que nous aurions dû appeler, c’est un langage d’écriture de serveur, ce qui est ce que nous pensions vraiment. Maintenant, je comprends que ce que nous avons, c’est un langage d’infrastructure en nuage. Une autre définition de la programmation système est le truc qui fonctionne dans le cloud.
  • Andrei Alexandrescu : J’ai quelques tests décisifs pour vérifier si quelque chose est un langage de programmation système. Un langage de programmation système doit pouvoir vous permettre d’écrire votre propre allocateur de mémoire dedans. Vous devriez être en mesure de forger un nombre dans un pointeur, puisque c’est ainsi que le matériel fonctionne.

La programmation système concerne-t-elle alors les hautes performances ? Les contraintes de ressources ? Le contrôle du matériel ? L’infrastructure du nuage ? Il semble, en gros, que les langages de la catégorie C, C++, Rust et D se distinguent par leur niveau d’abstraction de la machine. Ces langages exposent les détails du matériel sous-jacent comme l’allocation/la disposition de la mémoire et la gestion des ressources à grain fin.

Une autre façon d’y penser : lorsque vous avez un problème d’efficacité, quelle liberté avez-vous pour le résoudre ? La partie merveilleuse des langages de programmation de bas niveau est que lorsque vous identifiez une inefficacité, il est en votre pouvoir d’éliminer le goulot d’étranglement par un contrôle minutieux des détails de la machine. Vectorisez cette instruction, redimensionnez cette structure de données pour la garder en cache, et ainsi de suite. De la même manière que les types statiques fournissent plus de confiance3 comme « ces deux choses que j’essaie d’ajouter sont définitivement des entiers », les langages de bas niveau fournissent plus de confiance que « ce code s’exécutera sur la machine comme je l’ai spécifié. »

En revanche, l’optimisation des langages interprétés est une jungle absolue. Il est incroyablement difficile de savoir si le runtime exécutera systématiquement votre code de la façon dont vous l’attendez. C’est exactement le même problème avec les compilateurs auto-parallisés – « l’auto-vectorisation n’est pas un modèle de programmation » (voir L’histoire d’ispc). C’est comme écrire une interface en Python, en pensant « bien j’espère certainement que celui qui appelle cette fonction me donne un int. »

Aujourd’hui : …alors qu’est-ce que la programmation de systèmes ?

Ceci me ramène à mon grief initial. Ce que beaucoup de gens appellent la programmation de systèmes, je pense juste à la programmation de bas niveau-exposant les détails de la machine. Mais qu’en est-il des systèmes alors ? Rappelez-vous notre définition de 1972:

  1. Le problème à résoudre est de nature générale et consiste en de nombreux sous-problèmes, généralement très variés.
  2. Le programme système est susceptible d’être utilisé pour soutenir d’autres logiciels et programmes d’application, mais peut aussi être un paquet d’application complet lui-même.
  3. Il est conçu pour une utilisation continue de « production » plutôt que pour une solution unique à un seul problème d’application.
  4. Il est susceptible d’évoluer continuellement dans le nombre et les types de fonctionnalités qu’il supporte.
  5. Un programme système nécessite une certaine discipline ou structure, à la fois au sein et entre les modules (c’est-à-dire, la « communication ») , et est généralement conçu et mis en œuvre par plus d’une personne.

Ces-ci semblent beaucoup plus comme des questions de génie logiciel (modularité, réutilisation, évolution du code) que des questions de performance de bas niveau. Ce qui signifie que tout langage de programmation qui donne la priorité à la résolution de ces problèmes est un langage de programmation de systèmes ! Cela ne signifie pas pour autant que tous les langages sont des langages de programmation systémique. Les langages de programmation dynamiques sont sans doute encore loin des langages de systèmes, puisque les types dynamiques et les idiomes comme « demander pardon, pas la permission » ne sont pas propices à une bonne qualité de code.

Que nous apporte cette définition, alors ? Voici une prise à chaud : les langages fonctionnels comme OCaml et Haskell sont beaucoup plus orientés systèmes que les langages de bas niveau comme C ou C++. Lorsque nous enseignons la programmation des systèmes aux étudiants de premier cycle, nous devrions inclure des principes de programmation fonctionnelle comme la valeur de l’immutabilité, l’impact des systèmes de types riches dans l’amélioration de la conception de l’interface, et l’utilité des fonctions d’ordre supérieur. Les écoles devraient enseigner à la fois la programmation des systèmes et la programmation de bas niveau.

Comme préconisé, y a-t-il une distinction entre la programmation des systèmes et un bon génie logiciel ? Pas vraiment, mais un problème ici est que le génie logiciel et la programmation de bas niveau sont souvent enseignés de manière isolée. Alors que la plupart des cours de génie logiciel sont généralement centrés sur Java « écrire de bonnes interfaces et des tests », nous devrions également enseigner aux étudiants comment concevoir des systèmes qui ont des contraintes de ressources importantes. Nous appelons peut-être la programmation de bas niveau « systèmes » parce que bon nombre des systèmes logiciels les plus intéressants sont de bas niveau (par exemple, les bases de données, les réseaux, les systèmes d’exploitation, etc.) Comme les systèmes de bas niveau ont de nombreuses contraintes, ils exigent de ses concepteurs qu’ils pensent de manière créative.

Un autre cadrage est que les programmeurs de bas niveau devraient chercher à comprendre quelles idées dans la conception de systèmes pourraient être adaptées pour faire face à la réalité du matériel moderne. Je pense que la communauté Rust a été excessivement innovante à cet égard, en regardant comment les bons principes de conception logicielle/programmation fonctionnelle peuvent être appliqués à des problèmes de bas niveau (par exemple les futures, la gestion des erreurs, ou bien sûr la sécurité de la mémoire).

Pour résumer, ce que nous appelons « programmation de systèmes », je pense qu’il faudrait l’appeler « programmation de bas niveau ». La conception de systèmes informatiques en tant que domaine est trop importante pour ne pas avoir son propre nom. Séparer clairement ces deux idées fournit une plus grande clarté conceptuelle sur l’espace de la conception de langage de programmation, et cela ouvre également la porte au partage des idées à travers les deux espaces : comment pouvons-nous concevoir le système autour de la machine, et vice versa ?

Veuillez diriger les commentaires vers ma boîte de réception à [email protected] ou Hacker News.

  1. Fait cool ici : deux des auteurs de cet article, R. Bergeron et Andy Van Dam, sont des membres fondateurs de la communauté graphique et de la conférence SIGGRAPH. Une partie d’un modèle continu où les chercheurs en graphisme définissent la tendance dans la conception des systèmes, c.f. l’origine de GPGPU.

  2. Lien obligatoire à Scalability ! Mais à quel COST ?

  3. Idéalement les types statiques sont des garanties à 100% (ou votre argent est remboursé), mais en pratique la plupart des langages permettent une certaine quantité de magie Obj.magic.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.