Dans un premier article, nous avons parlé des programmes tels qu'ils étaient conçus à l'origine de l'informatique, c'est à dire du code machine produit par simple traduction d'un code source en code binaire (dans le cas de l'assembleur) ou de façon plus sophistiquée par compilation d'un code source en code binaire (dans le cas des langages compilés).
Pourquoi ces autres possibilités ?
Assembleur : vraiment trop relou
Nous avons vu que l'assembleur était tout simplement un truc de malade : pour pouvoir programmer en assembleur, il faut savoir comment fonctionne le processeur ce qui est très compliqué ; du coup comme le langage est de très bas niveau, il faut écrire des centaines de lignes pour faire de choses très simples.
On en réserve donc l'usage à des cas très spécifiques, et ça reste une affaire de spécialiste (ou de curieux passionné).
Langage compilé : encore trop compliqué pour la ménagère de 50 ans
Les langages compilés sont donc apparus pour permettre de simplifier la programmation : il n'y a plus besoin de connaître le fonctionnement du processeur ; le langage est de plus haut niveau et propose des instructions qui sont plus évoluées en ce sens qu'elles nous permettent plus directement de faire ce que l'on veut sans se soucier de la façon dont le processeur va faire (ou plus précisément de la façon dont le compilateur va traduire notre instruction de haut niveau en dizaines ou centaines d'instruction en code machine).
Donc vous me direz que tout est bien. Mais en fait non.
L'utilisation d'un langage compilé nécessite toujours de comprendre, certes de façon beaucoup moins pointue, un certain nombre de mécanismes internes liés à l'électronique mise en oeuvre ; pour être plus précis, le développeur doit toujours se soucier de l'utilisation de la mémoire vive (RAM).
Tout programme manipule des données, et ces données sont lues et écrites dans la mémoire vive. La mémoire vive ce sont des composants électroniques qui comportent pleins de petits casiers dans lesquels le processeur range les données pour les retrouver quand il en a besoin. Chaque casier à une adresse précise qui permet de le référencer.
Le programmeur dispose d'une abstraction pour ces cases en mémoire : ce sont les variables. Pour chaque donnée qu'il doit gérer, le programmeur déclare une variable (montantHT, tauxTVA, ageDuCapitaine) qu'il peut ensuite manipuler simplement. Or, une variable c'est tout simplement l'abstraction de l'adresse d'une case en mémoire.
Mais où ça devient un peu compliqué, c'est qu'une case en mémoire a une taille fixe tandis que les données manipulées dans un programme ont des tailles différentes et/ou variables. Par exemple, un age est un chiffre compris entre 0 et 120 (soyons optimistes) et tiendra dans une seule case, par contre un nom a une taille potentiellement beaucoup plus grande et nécessitera entre une et deux cases par lettre (selon qu'on gère ou non les alphabets internationaux).
La conséquence de ceci, c'est qu'il faut avoir conscience de la taille occupée en mémoire par chaque type de données (par exemple pour chaque type numérique : entier signé, entier non signé, nombre à décimale, sur telle ou telle plage de valeurs possible ...). De plus, un programme ne peut généralement pas connaître à l'avance toutes les variables qu'il doit utiliser car cela va dépendre des interactions avec l'utilisateur du programme (par exemple de la quantité de données qu'il va saisir au clavier) ; du coup, le programme doit réserver dynamiquement (à l'exécution) des plages de la mémoire pour pouvoir y stocker les données saisies. Et ce n'est pas tout, une fois que la variable n'est plus nécessaire, il faut libérer la réservation afin que les cases en mémoire soient à nouveau disponibles pour un prochain usage, faute de quoi la mémoire étant en quantité limitée elle sera totalement utilisée et votre programme ne pourra pas continuer à fonctionner ; il plantera purement avec un joli message du genre "out of memory / mémoire insuffisante".
Cette gestion de la mémoire est délicate et constitue la principale source de bugs dans les programmes. Elle implique un minimum de compréhension du fonctionnement interne d'un ordinateur et donc de formation (et énormément de rigueur).
De ce fait, les langages compilés sont généralement des outils réservés à des professionnels aguerris, et ne sont pas vraiment accessible à un individu lamda.
Langage compilé : outillage pénible
Imaginons un peu que nous voulions essayer d'écrire un programme pour réaliser une idée quelconque. Déjà, il va falloir écrire le programme avec un éditeur de texte, bon ça c'est de toute façon inévitable. Ensuite, il va falloir le compiler avec un premier outil, le compilateur. Ensuite, il va falloir lancer l'édition de lien avec le linker. Enfin, nous aurons un exécutable que nous pourrons lancer pour voir comment ça marche. Et si ça fait pas que qu'on voulait, ce qui est le cas dans 99 % au début, il faut modifier le code source, recompiler etc.
Première précision : lancer un compilateur ou un linker, c'est pas forcément simple : il peut y avoir des quantités de paramètres très techniques à préciser.
Seconde précision : une compilation ou une édition de lien ce n'est pas immédiat. Et selon la taille du code source ou le nombre de librairies externes à linker ça peut prendre pas mal de temps.
Bref, tout ça pour dire que de l'idée à la réalisation, il y a un certain nombre d'étapes et que ce n'est pas une solution idéale quand on veut tester rapidement des idées. Et on voudrait bien aller plus vite et se passer de toutes ces étapes.
Un dernier mot : la situation décrite ci-dessus n'est plus la situation actuelle. Aujourd'hui, on dispose d'IDE (Integrated Development Environment) qui combinent tous les outils en un et en facilitent énormément l'utilisation. En outre, les progrès des compilateurs, plus la considérable augmentation de la puissance des processeurs depuis 30 ans, font qu'aujourd'hui on peut faire du prototypage rapide d'idées avec des langages compilés. Mais ce n'était pas le cas à une époque.
Principes / avantages / inconvénients
Principe
Vu que le processeur est trop crétin pour comprendre ce qu'on lui dit (c'est toujours ce que disent les crétins incapables de se faire comprendre), et que Monsieur X est bien trop important pour s'abaisser à apprendre sa langue, il va recruter un domestique qui se chargera de l'intendance : on lui donnera nos ordres et il se démerdera avec le petit personnel (le processeur et la ram).
Bien sur, le domestique étant de basse extraction, il ne parlera pas notre langage châtié, mais l'effort pour se faire comprendre sera moindre, il faudra juste s'abaisser à son niveau.
Bon, ça c'était une image à la sauce grand siècle. Plus concrètement, on ne va plus écrire un programme dans le langage du processeur (plus ou moins indirectement, cf assembleur/compilateur), mais dans un autre langage qui sera compris par un programme chargé de l'exécuter.
Ce programme saura faire un certain nombre de choses et aura donc un jeu d'instructions, comme le processeur, mais ce seront des instructions beaucoup plus simples et compréhensibles ; en outre, on n'aura plus à se soucier de la mémoire (enfin presque).
Avantages
Le premier avantage, c'est la simplicité du langage. Enfin, on a un langage accessible au plus grand nombre, sans devoir étudier pendant des semaines, sans se prendre trop la tête.
Le second avantage, c'est la facilité d'utilisation. Plus besoin de passer par une lourde phase de compilation, aussitôt le code est il écrit qu'on peut le tester pour voir ce que ça donne et ajuster en conséquence.
Le troisième avantage, c'est la portabilité du programme. La portabilité c'est la capacité à utiliser notre programme sur une machine avec un autre système d'exploitation, ou carrément sur une machine avec une autre architecture processeur. En effet, comme on s'adresse à un programme intermédiaire, il suffira que ce programme existe sur différents OS et matériels pour que notre programme fonctionne partout. Elle est pas belle la vie ?
Inconvénients
Comme toujours, les inconvénients découlent des avantages.
Vu qu'on a un intermédiaire entre nous et le processeur, l'exécution du programme est beaucoup moins rapide que dans le cas d'un langage compilé. Mais cet inconvénient est à relativiser pour deux raisons. Déjà, on n'a pas toujours besoin de vitesse : un programme peut se traîner sans que ce soit gênant, et un programme qui fait peu de calcul et beaucoup d'entrées/sorties avec des périphériques qui sont lents par nature (beaucoup de lecture/écriture de données sur disque par exemple) sera peu ralenti par rapport à une version compilée. Ensuite, les processeurs d'aujourd'hui sont tellement rapide qu'on arrive quand même à des vitesses satisfaisantes. Et pour finir, la plupart des outils actuels incorporent des optimisations de compilation à la volée (à l'exécution, on parle de compilation JIT, Just In Time) qui leur permettent d’accélérer grandement leur vitesse (le meilleur exemple étant le javascript).
Pour ma part, je dirais que le principal inconvénient vient du fait que ces langages sont conçus pour être accessibles à tout le monde, ils sont trop démocratiques en quelque sorte. Pour ce faire, leurs concepteurs ont du faire des choix et sacrifier des possibilités avancées pourtant indispensables pour optimiser ou structurer proprement un programme. Et en cachant la complexité, ils gênent aussi la possibilité de comprendre leur fonctionnement interne et la façon d'optimiser l'utilisation de la mémoire, ce qui est une gêne pour les développeurs professionnels soucieux de qualité. Une fois encore, ceci est à relativiser en fonction de la criticité des applications développées (en gros, pour une application pas essentielle et qui n'évoluera pas beaucoup, on se fout qu'elle soit mal optimisée et non maintenable tant qu'elle coûte 3 fois moins cher à produire), et du coût toujours en baisse de la mémoire (acheter de la mémoire coûte moins cher que d'en optimiser l'usage par les programmes).
On est ici en plein sur un conflit de génération entre informaticiens d'antan soucieux d'économiser les ressources et pointus techniquement, et informaticiens d'aujourd'hui qui se foutent royalement de comment ça marche tant que ça répond rapidement à leur besoin.
Ajoutons au passage, en ces jours de COP21, qu'on se soucie aujourd'hui d'écologie en informatique, et que l'efficacité d'un programme qui va moins solliciter le matériel, et donc moins consommer d'électricité, et donc moins polluer est devenue un sujet.
Les solutions
Les langages interprétés.
Une application directe des principes expliqués ci-avant. On a un programme, appelé "interpréteur" (ou runtime, ou moteur d'exécution) qui va lire le code source et l'exécuter directement.
Le plus connu est le Basic, un langage pour débutants. Ceci dit, la plupart des langages Basic modernes sont aujourd'hui compilés.
Non aujourd'hui, si il faut parler de langage interprété, le plus répandu est sans doute le Javascript utilisé pour dynamiser les pages web. Ou le PHP utilisé pour le plus grand nombre de sites web sur la planète.
Le javascript devient de plus en plus performant grâce au fait que les moteurs js incorporent des techniques de compilation à la volée (JIT). Le principe de la compilation JIT est que l'interpréteur analyse le code ; quand il détecte des sections qui vont être appelées de nombreuses fois, il fait un calcul pour voir si il sera plus intéressant de supporter le coût d'une compilation qui surviendra une seule fois et permettra d'exécuter le code plus vite, plutôt que de l'interpréter. On a ainsi le meilleur des deux mondes, mais ça reste quand même moins rapide qu'un programme compilé.
PHP vient de sortir en version 7 et intègre également des mécanismes JIT ce qui lui permet d'aller 2X plus vite (que la version précédente, la v5, cherchez pas la v6 elle n'existe pas) ; et vu que 50% environ des sites web dans le monde sont écrits en PHP et qu'ils devraient migrer facilement sur cette version, le web mondial va accélérer en 2016 et 2017. Classe !
Les machines virtuelles
Alors une machine virtuelle, c'est un programme qui simule de façon plus ou moins complète un ordinateur.
Si les plateformes techniques de virtualisation permettent de simuler un ordinateur au complet avec toute son électronique, nous nous intéresserons ici uniquement aux programmes qui simulent le fonctionnement d'un processeur et que nous appellerons "machine virtuelle". On peut d'ailleurs voir ces solutions assez anciennes comme les précurseurs des solutions de virtualisation actuelles qui sont à l'origine de l'essor de l'informatique dans le cloud.
Alors une machine virtuelle simule un processeur : elle a donc une architecture et à ce titre un jeu d'instruction. Et donc, on peut écrire des programmes qui vont être compilés dans ce jeu d'instruction, ou même pourquoi pas interprétés (même si à la base, ça n'a pas été conçu dans cet esprit).
Alors, on revient à nouveau à de la compilation. Quel avantage du coup ? Hé bien, ce processeur virtuel est plus simple et la compilation de code source beaucoup plus rapide que quand on compile pour un vrai processeur. On ne parle d'ailleurs normalement pas de code binaire pour le résultat final mais de p-code.
Mais les vrais avantages sont ailleurs. Ces processeurs virtuels prennent en charge une partie des tâches habituellement dévolues au programmeur, et en particulier la gestion de la mémoire. Un mécanisme appelé ramasse-miette (garbage collector) gère automatiquement le fait de libérer la mémoire qui n'est plus utilisée ce qui est un progrès considérable. Les développeurs n'ont plus d'accès direct à la mémoire et ne peuvent plus faire d'erreur au niveau des allocations mémoire (qui sont gérées par le processeur d'après les demandes simplifiées faites par les programmeurs).
Ce qui est amusant c'est que ces machines virtuelles font également usage de compilation JIT pour améliorer leur vitesse d'exécution. Mais alors que ces techniques sont récemment adoptées par les interpréteurs javascript, elles sont développées depuis au moins une quinzaine d'années.
Comme pour les interpréteurs on a la possibilité d'avoir du code portable entre OS/architectures processeurs dès lors que la machine virtuelle existe sur les environnements cibles.
Sur les 3 technologies principales utilisées aujourd'hui pour le Web, deux sont basées sur une machine virtuelle : Dot Net de Microsoft et Java de Oracle (anciennement Sun), la troisième étant PHP qui est interprété (au sein du serveur HTTP).
Microsoft et Oracle utilisent la virtualisation dans des objectifs différents.
Microsoft n'a pas porté sa machine virtuelle, appelée CLR (Common Language Runtime, moteur d'exécution commun pour les langages), sur d'autres OS que les Windows ni à priori (suis pas un spécialiste Microsoft) sur d'autres architectures processeur que x86/x64 (à voir si la plateforme DotNet est supportée sur Windows RT, la version de Windows pour processeurs ARM). Mais Microsoft permet aux développeurs d'écrire leur code source dans de nombreux langages différents et de les compiler en p-code CLR ; ainsi le développeur peut choisir le langage qui supporte le paradigme qu'il préfère ou celui dont il préfère le langage pour des raisons de convenance personnelle. C# est le langage objet de la plateforme DotNet et il est le plus moderne, mais il est possible d'utiliser des version Microsoft de Basic par exemple pour les développeurs moins aguerris.
Oracle pour sa part a porté sa machine virtuelle appelée JVM (Java Virtual Machine), ou permis à d'autres de le faire en publiant les spécifications et en fournissant des outils de certification, sur à peu près tous les OS et architectures processeurs existants. Mais Oracle ne fournit qu'un seul langage (avec son compilateur et tout le nécessaire) pour développer sur le JVM : le langage Java.
Le fait que le langage Java soit extrêmement portable explique le fait qu'au début du Web de nombreux sites utilisaient des Applets. Les Applets sont des composants développés en Java et qui sont envoyés par le serveur Http avec les pages web qui les embarquent. Ils sont exécutés sur le poste client qui doit bien sur disposer d'une JVM. Pour diverses raisons, cette technologie est aujourd'hui tombée en désuétude, remplacée par le javascript (interprété, l'interpréteur est embarqué dans le navigateur, pas besoin d'installer un JRE - Java Runtime Environment, environnement d'exécuton Java - sur le poste client en plus du navigateur. Le JRE inclut la JVM).
Le langage Java est un langage Objet. Pour programmer en Java, il faut donc maîtriser le paradigme Objet ce qui fait de cette plateforme un outil plutôt réservé aux professionnels (le langage Java est très simple mais le mode de pensée Objet nécessite un long apprentissage). Mais depuis quelques années, on voit foisonner divers langages qui peuvent se compiler en p-code exécutable par la JVM (on utilise en général le terme bytecode même si il peut être source de confusion) et qui améliorent le langage Java (un peu trop académique, verbeux et peu productif) ou permettent le support d'autres paradigmes (simple scripting, programmation fonctionnelle) : Jython d'IBM (Pyton compilé), Scala (paradigme fonctionnel), Ruby, Groovy... Certains de ces outils permettent d'interpréter du code à la volée (sans passer par la phase de compilation).
Conclusion
Voila pour ce rapide tour d'horizon.
On peut constater que chaque progrès sert de base au progrès suivant, et que quelquefois des idées anciennes laissées de côté reprennent de l'intérêt en fonction de l'évolution des technologies.
L'informatique est aujourd'hui un métier de masse ; des écoles d'ingénieurs de qualité très variable forment à tour de bras des armées de développeurs pour alimenter l'appétit vorace des sociétés de service en chair fraîche. Bien souvent ces développeurs sont formés sur une technologie pour être directement employables et n'ont pas forcément conscience de l'ensembles des possibilités. Espérons que ceci changera car ces jeunes trop vite formés et parfois peu intéressés par le métier, simplement attirés par le mirage d'un gros salaire et d'un premier emploi facile à décrocher, décrocheront rapidement pour une partie non négligeable et iront grossir le flot des informaticiens chômeurs.