
Au cours de la dernière décennie, depuis l'avènement du langage C, de nombreux langages de programmation intéressants ont été créés. Certains d'entre eux sont encore utilisés, d'autres ont influencé la prochaine génération de langues, la popularité du troisième s'est ternie. Pendant ce temps, archaïque, controversé, primitif, rendu dans les pires traditions de sa génération de langages C (et de ses héritiers) plus vivant que tous les êtres vivants.
Criticism C est un genre épistolaire classique pour notre industrie. Cela semble plus fort, puis plus silencieux, mais ces derniers temps, il a littéralement été magnifique. Un exemple est une traduction de l'article de David Ciswell «C n'est pas un langage de bas niveau», publié sur notre blog il y a quelque temps. On peut dire différentes choses sur C, il y a vraiment beaucoup d'erreurs désagréables dans la conception du langage, mais refuser C au "bas niveau" c'est trop!
Afin de ne pas tolérer une telle injustice, j'ai pris courage et essayé de décider ce qu'était un langage de programmation de bas niveau et quelles pratiques ils en voulaient, après quoi je suis passé en revue les arguments des critiques C.C'est ainsi que cet article s'est avéré.
Table des matières
Arguments de critique C
Voici certains des arguments des critiques de C, y compris ceux énumérés dans un article de David Chiznell:
- La machine abstraite du langage C est trop similaire à l'architecture obsolète du PDP-11, qui a depuis longtemps cessé de correspondre au dispositif des processeurs modernes populaires.
- L'inadéquation entre une machine abstraite et le dispositif de machines réelles complique le développement de l'optimisation des compilateurs de langage.
- L'incomplétude et la complexité de la norme de langage entraînent des écarts dans les implémentations standard.
- La domination des langages de type C ne permet pas d'explorer d'autres architectures de processeur.
Déterminons d'abord les exigences pour un langage de bas niveau, après quoi nous revenons aux arguments donnés.
Langage de programmation de bas niveau
Il n'y a pas de définition universellement acceptée d'une langue de bas niveau. Mais avant de discuter de questions controversées, il est souhaitable d'avoir au moins quelques exigences initiales pour l'objet du différend.
Personne ne prétendra que le langage d'assemblage est au niveau le plus bas. Mais sur chaque plate-forme, il est unique, donc le code dans un tel langage ne peut pas être portable. Même sur une plate-forme rétrocompatible, vous devrez peut-être utiliser de nouvelles instructions.
D'ici suit la première exigence pour un langage de bas niveau: il doit conserver les fonctionnalités communes aux plates-formes populaires . Autrement dit, le compilateur doit être portable. La portabilité du compilateur simplifie le développement de compilateurs de langage pour les nouvelles plates-formes, et la variété des plates-formes prises en charge par les compilateurs élimine la nécessité pour les développeurs de réécrire les programmes d'application pour chaque nouvelle machine.
La première exigence est en contradiction avec les souhaits des développeurs de programmes spéciaux: langages de programmation, pilotes, systèmes d'exploitation et bases de données hautes performances. Les programmeurs qui écrivent ces programmes souhaitent pouvoir optimiser manuellement, travailler directement avec la mémoire, etc. En un mot, un langage de bas niveau devrait permettre de travailler avec les détails de l'implémentation de la plateforme .
Trouver un équilibre entre ces deux exigences - identifier les aspects communs aux plateformes et accéder à autant de détails que possible - est une raison fondamentale de la difficulté de développer un langage de bas niveau.
Notez que les abstractions de haut niveau ne sont pas si importantes pour un tel langage - il est plus important pour lui de servir de contrat entre la plateforme, le compilateur et le développeur. Et s'il y a un contrat, il faut alors un langage indépendant de la norme de mise en œuvre particulière .
Notre première exigence - les fonctionnalités communes aux plates-formes cibles - est exprimée dans une machine à langage abstrait, nous allons donc commencer la discussion avec C.
Il ne s'agit pas seulement de PDP-11
La plate-forme dans laquelle le langage C est apparu est PDP-11. Il est basé sur l' architecture von Neumann traditionnelle, dans laquelle les programmes sont exécutés séquentiellement par le processeur central, et la mémoire est une bande plate, où les données et les programmes sont stockés. Une telle architecture est facilement implémentée dans le matériel et, au fil du temps, tous les ordinateurs à usage général ont commencé à l'utiliser.
Les améliorations modernes apportées à l'architecture de von Neumann visent à éliminer son principal goulot d'étranglement - les retards dans l'échange de données entre le processeur et la mémoire (anglais von Neuman bottleneck ). La différence de mémoire et de performances du processeur a conduit à l'apparition de sous-systèmes de mise en cache des processeurs (à un niveau et plus tard à plusieurs niveaux).
Mais même les caches de nos jours ne suffisent pas. Les processeurs modernes sont devenus superscalaires. Les retards lorsque les instructions reçoivent des données de la mémoire sont partiellement compensés par l'exécution extraordinaire ( parallélisme au niveau des instructions) des instructions, couplée au prédicteur de branchement .
La machine abstraite séquentielle C (et de nombreux autres langages) imite le travail non pas spécifiquement de PDP-11, mais de tout ordinateur disposé selon le principe de l'architecture de von Neumann. Il comprend des architectures construites autour de processeurs à un seul cœur: ordinateur de bureau et serveur x86, ARM mobile, issus de la scène de Sun / Oracle SPARC et IBM POWER.
Au fil du temps, plusieurs cœurs de traitement ont commencé à être intégrés dans un seul processeur, ce qui a rendu nécessaire de maintenir la cohérence des caches de chaque cœur et a nécessité des protocoles d'interaction internucléaire. L'architecture de Von Neumann a ainsi été adaptée à plusieurs cœurs.
La version originale de la machine abstraite C était séquentielle, ne reflétant pas la présence de threads d'exécution de programme interagissant à travers la mémoire. L'apparition du modèle de mémoire dans la norme a élargi les capacités de la machine abstraite en parallèle.
Ainsi, l'affirmation selon laquelle la machine C abstraite a longtemps été incompatible avec la structure des processeurs modernes ne concerne pas tant un langage spécifique, mais des ordinateurs qui utilisent l'architecture von Neumann, y compris en exécution parallèle.
Mais en tant que praticien, je tiens à noter ce qui suit: nous pouvons supposer que l'approche Fonneimann est obsolète, nous pouvons supposer qu'elle est pertinente, mais cela n'annule pas le fait que les architectures polyvalentes populaires d'aujourd'hui utilisent des dérivés des approches traditionnelles.
Le mode de réalisation standardisé et portable de l'architecture von Neumann - la machine C abstraite - est commodément implémenté sur toutes les principales plates-formes et jouit donc à juste titre de sa popularité en tant qu'assembleur portable.
Optimisation des compilateurs et du langage de bas niveau
Notre deuxième exigence pour un langage de bas niveau est l'accès aux détails d'implémentation de bas niveau de chacune des plateformes populaires. Dans le cas de C, il s'agit d'un travail direct avec de la mémoire et des objets en tant que tableau d'octets, la possibilité de travailler directement avec des adresses d'octets et une arithmétique de pointeur avancée.
Les critiques de C soulignent que la norme linguistique donne trop de garanties concernant, par exemple, l'emplacement des champs individuels dans les structures et les associations. Avec des pointeurs et des mécanismes primitifs de boucles, cela complique le travail de l'optimiseur.
En effet, une approche plus déclarative permettrait au compilateur de résoudre indépendamment les problèmes d'alignement des données en mémoire ou l'ordre optimal des champs dans les structures; et les cycles de niveau supérieur offrent la liberté dont vous avez besoin lors de la vectorisation.
La position des développeurs C dans ce cas est la suivante: un langage de bas niveau devrait lui permettre de fonctionner à un niveau suffisamment bas pour que le programmeur puisse résoudre indépendamment les problèmes d'optimisation. En C, il est possible de travailler comme un compilateur, en choisissant, par exemple, des instructions SIMD et en plaçant correctement les données en mémoire.
En d'autres termes, notre exigence d'accès aux détails d'implémentation de chaque plateforme entre en conflit avec les souhaits des développeurs d'optimiser les compilateurs précisément en raison de la présence d'outils de bas niveau.
Fait intéressant, Chiznell dans un article intitulé «C n'est pas un langage de bas niveau» affirme paradoxalement que C est trop bas, ce qui indique l'absence d'outils de haut niveau. Mais les praticiens ont précisément besoin d'outils de bas niveau, sinon le langage ne peut pas être utilisé pour développer des systèmes d'exploitation et d'autres programmes de bas niveau, c'est-à-dire qu'il ne satisfera pas la seconde de nos exigences.
En me distrayant de la description des problèmes d'optimisation à savoir C, je tiens à noter qu'à l'heure actuelle, aucun effort n'est moins investi dans l'optimisation des compilateurs de langages de haut niveau (les mêmes C # et Java) que dans GCC ou Clang. Les langages fonctionnels ont également suffisamment de compilateurs efficaces: MLTon, OCaml et autres. Mais les développeurs du même OCaml peuvent encore se targuer de performances au mieux à la moitié de la vitesse du code C ...
Standard comme un bien absolu
Dans son article, Chiznell cite les résultats d'une enquête menée en 2015: de nombreux programmeurs ont fait des erreurs en résolvant les problèmes de compréhension des normes C.
Je suppose que l'un des lecteurs avait affaire à la norme C. J'ai une version papier de C99, 900 pages de publicités. Ce n'est pas une spécification laconique avec un volume de moins de 100 pages et pas un ML standard léché de 300. Plaisir du travail personne n'obtient la norme C: ni développeurs de compilateurs, ni développeurs de documents, ni programmeurs.
Mais il faut comprendre que la norme C a été développée après coup, après l'apparition de nombreux dialectes compatibles «presque à peine placés». Les auteurs de l'ANSI C ont fait un excellent travail en résumant les implémentations existantes et en les couvrant avec d'innombrables «béquilles» de non orthogonalité dans la conception du langage.
Il peut sembler étrange que quelqu'un se soit engagé à mettre en œuvre un tel document. Mais C a été implémenté par de nombreux compilateurs. Je ne raconterai pas les contes des autres sur le zoo du monde UNIX de la fin des années 80, d'autant plus qu'à cette époque je ne le considérais pas avec beaucoup de confiance et seulement jusqu'à cinq ans. Mais, évidemment, tout le monde dans l'industrie avait vraiment besoin d'une norme.
La grande chose est qu'elle existe et est implémentée par au moins trois grands compilateurs et de nombreux compilateurs plus petits, qui ensemble prennent en charge des centaines de plates-formes. Aucune des langues concurrentes C, revendiquant la couronne du roi des langues de bas niveau, ne peut se targuer d'une telle diversité et polyvalence.
En fait, la norme C actuelle n'est pas si mauvaise. Un programmeur plus ou moins expérimenté est capable de développer un compilateur C non optimisant dans un délai raisonnable, ce qui est confirmé par l'existence de nombreuses implémentations semi-amateurs (les mêmes TCC, LCC et 8cc).
Avoir une norme généralement acceptée signifie que C satisfait la dernière de nos exigences pour un langage de bas niveau: ce langage est construit sur une spécification, pas une implémentation spécifique.
Architectures alternatives - informatique spéciale
Mais Lifewell cite un autre argument, revenant au dispositif des processeurs polyvalents modernes qui implémentent les options d'architecture von Neumann. Il affirme qu'il est logique de changer les principes du processeur central. Encore une fois, cette critique n'est pas spécifique au C, mais au modèle le plus élémentaire de programmation impérative.
En effet, il existe de nombreuses alternatives à l'approche traditionnelle avec exécution séquentielle de programmes: modèles SIMD dans le style GPU, modèles dans le style d'une machine Erlang abstraite, et autres. Mais chacune de ces approches a une applicabilité limitée lorsqu'elle est utilisée dans un processeur central.
Les GPU, par exemple, multiplient remarquablement les matrices dans les jeux et l'apprentissage automatique, mais ils sont difficiles à utiliser pour le lancer de rayons. En d'autres termes, ce modèle convient aux accélérateurs spécialisés, mais ne fonctionne pas pour les processeurs à usage général.
Erlang fonctionne très bien dans un cluster, mais il est difficile de faire un tri rapide efficace ou une table de hachage rapide. Le modèle des acteurs indépendants est mieux utilisé à un niveau supérieur, dans un grand cluster, où chaque nœud est toujours la même machine haute performance avec un processeur traditionnel.
Pendant ce temps, les processeurs modernes compatibles x86 ont longtemps inclus des ensembles d'instructions vectorielles similaires au GPU dans leur objectif et leurs principes de fonctionnement, mais préservant le circuit général du processeur dans le style von Neumann dans son ensemble. Je ne doute pas que des approches assez générales de l'informatique seront incluses dans les processeurs populaires.
Il existe une telle opinion faisant autorité : l'avenir réside dans les accélérateurs programmables spécialisés. Sous des morceaux de fer aussi extraordinaires, il est vraiment logique de développer des langages avec une sémantique particulière. Mais un ordinateur polyvalent était et reste similaire au très PDP-11, pour lequel les langages impératifs de type C sont si bien adaptés.
C vivra
Il y a une contradiction fondamentale dans l'article de Chiznell. Il écrit que pour assurer la vitesse des programmes C, les processeurs imitent la machine C abstraite (et le PDP-11 oublié depuis longtemps), après quoi il souligne les limites d'une telle machine. Mais je ne comprends pas pourquoi cela signifie que "C n'est pas un langage de bas niveau".
En général, il ne s'agit pas des défauts de C en tant que langage, mais de la critique des architectures communes de style von Neumann et du modèle de programmation qui en découle. Mais jusqu'à présent, il ne semble pas que l'industrie soit prête à abandonner l'architecture familière (du moins pas dans les processeurs à usage général).
Malgré la disponibilité de nombreux processeurs spécialisés tels que les GPU et les TPU, l'architecture von Neumann est actuellement sous contrôle et l'industrie a besoin d'un langage lui permettant de fonctionner au plus bas niveau possible dans le cadre de l'architecture la plus populaire. Un assez simple, porté sur des dizaines de plates-formes et un langage de programmation standardisé est C (et sa famille immédiate).
Pour autant, C a suffisamment de défauts: une bibliothèque archaïque de fonctions, un standard complexe et incohérent, et des erreurs de conception grossières. Mais, apparemment, les créateurs de la langue ont quand même fait quelque chose de bien.
D'une manière ou d'une autre, nous avons toujours besoin d'un langage de bas niveau, et il a été spécialement conçu pour les ordinateurs Fonneimann populaires. Et que C soit obsolète, mais apparemment, tout successeur devra toujours s'appuyer sur les mêmes principes.