Java a été conçu pour fonctionner sur une machine virtuelle afin de permettre la portabilité des programmes.
Cependant .NET a été conçu dès le départ spécifiquement pour Windows.
Alors, quelle est la raison pour laquelle les applications .NET sont compilées en bytecode pour le CLR?
Était-ce simplement pour copier Java? Ou y a-t-il un avantage technique à simplement compiler en natif?
Compiler en certains bytecode est une vieille tradition. UCSD P-code existait en 1978 et avait de nombreux précurseurs. Aujourd'hui, [~ # ~] llvm [~ # ~] peut être considéré comme un bytecode, ciblé par Clang/LLVM suite de compilation à l'avance et [~ # ~] gccjit [~ # ~] peut être considéré comme un JIT lié à [~ # ~] gcc [~ # ~] ( avec [~ # ~] gimple [~ # ~] sorte d'être quelque interne bytecode).
(Par conséquent, le bytecode, JIT, ... a des significations assez floues aujourd'hui; le sens le plus large de JIT est la compilation à l'intérieur du processus exécutant le code compilé.)
Le bytecode JVM a été initialement implémenté comme interprète. Mais Java devient assez populaire pour obtenir [~ # ~] jit [~ # ~] JVM basé (et Sun a beaucoup investi dans la technologie JIT, donc ceci aidé Java pour réussir).
Et JIT existait il y a longtemps (au début des années 1980, par exemple dans les machines LISP, et même en 1960 sur l'ordinateur CAB 500 et autres), avant même que le nom ne soit utilisé. De nombreuses implémentations LISP ou Smalltalk courantes avaient des compilateurs JIT (et aujourd'hui, [~ # ~] sbcl [~ # ~] est entièrement JIT).
À ma connaissance, Microsoft a conçu le bytecode CLR pour être compilé JIT (d'où des compromis différents dans son bytecode par rapport à la JVM). Et il a récemment publié son implémentation sous la forme logiciel open-source et l'a porté à Linux (avant cela, Mono existait sous Linux).
Un bytecode est souvent plus compact qu'un binaire natif - exécutables , il peut être rendu portable sur plusieurs architectures (par exemple x86 32 bits et x86-64 et aussi ARM 32 bits, ARM/Aarch64, ...) et pourrait être conçu pour éviter (ou au moins adoucir) les enfers de dépendance .
Un gros avantage de la compilation JIT est que les parties VM peuvent recompiler certaines parties du bytecode basé sur des informations contextuelles dynamiques (par exemple profilage, appel introspection de pile =, ...) du code. Certaines infrastructures JIT comme libjit , asmjit , [~ # ~] llvm [~ # ~] , = [~ # ~] gccjit [~ # ~] , ... ne faites pas cela (cependant, l'implémentation qui les utilise pourrait le faire en utilisant plusieurs fois l'infrastructure JIT), mais la plupart des implémentations JVM ou CLR industrielles le font (et certaines personnes appellent JIT uniquement cette compilation dynamique paresseuse à la demande; pour moi, JIT n'est qu'un mot à la mode pour la compilation dynamique à l'exécution). C'est difficile ou impossible avec AOT compilation (au moins, nécessite [~ # ~] lto [~ # ~] ), et est impossible si vous voulez à faire optimisation guidée par le profil dynamiquement à l'exécution (comme le font la plupart des implémentations JVM ou CLR JIT). De plus, un bytecode VM n'a pas besoin de compiler JIT tout le bytecode, mais seulement les parties les plus utilisées (comme HotSpot le fait) et continue d'interpréter le code froid rarement utilisé.
Les implémentations JIT peuvent également coopérer beaucoup plus (et mieux ...) avec des garbage collectors sophistiqués .
PS. Je ne connais rien de Windows. Je ne l'ai jamais utilisé. J'utilise Linux depuis 1994 et Unix depuis 1987.
Windows n'est pas une plateforme unique et homogène. Lorsque la première version du clr a été publiée, elle ciblait non seulement la famille de systèmes Windows 98 traditionnelle (qui ne fonctionnait que sur x86), mais également les fenêtres nt 4 (x86, ppc, alpha, mips). La prise en charge de Windows CE (qui fonctionnait sur x86, sh, arm, mips et ppc) a été ajoutée dans la version 1.1, tandis que les cibles ia64 ("itanium") et x86-64 sur nt ont été ajoutées dans la version 2. Il est fort probable que le les développeurs savaient que la plupart ou la totalité de ces plates-formes devraient être prises en charge au début du projet. En fait, il semble probable que le fait que les éditeurs de logiciels indépendants ne tenaient pas à prendre en charge autant de plates-formes matérielles et aient tendance à ne publier que des versions x86 des applications figurait dans la décision de Microsoft de poursuivre le développement du système.
Eric Lippert a une bonne explication des raisons pour lesquelles les langages .NET ciblent IL au lieu de générer directement des fichiers binaires intitulés Pourquoi IL? .
La raison en est que le coût/effort de développement de votre compilateur est moins cher/plus simple avec cette approche. Votre compilateur de langage de haut niveau génère un langage intermédiaire commun. Cela facilite l'ajout de nouvelles langues car il n'y a qu'une seule plateforme pour générer du code. Ensuite, vous avez vos compilateurs de 2e étape qui prennent ce langage intermédiaire et produisent le binaire spécifique à la plate-forme (architecture du système d'exploitation et du processeur).
De cette façon, chaque fois que vous ajoutez la prise en charge d'une nouvelle plate-forme (comme un nouveau processeur), il vous suffit d'écrire un seul compilateur qui compile IL sur la nouvelle plate-forme.
De nombreuses fonctionnalités du système de types .NET, en particulier les types génériques qui ont été ajoutés en 2.0 mais (d'après ce que je comprends) anticipés dès le départ, rendent littéralement impossible de compiler du code pour tous les types qu'un programme pourrait utiliser avant lui. démarre l'exécution (puisque les combinaisons de type utilisées par un programme peuvent être affectées de manière arbitraire par les entrées reçues par un programme, et bien que le nombre de types discrets réellement utilisés par un programme au cours d'une seule exécution doit être limité, le nombre de types qu'un programme peut utiliser en réponse à diverses entrées ne doivent pas nécessairement l'être). Bien que la génération de code dynamique soit possible même sans machine virtuelle, l'utilisation d'une machine virtuelle le rend beaucoup plus facile et plus sûr.
En outre, l'efficacité du garbage collection dans un environnement multithread peut être considérablement améliorée en donnant au garbage collector la possibilité de bloquer simultanément tous les threads qui pourraient faire usage de références à des objets garbage collection. Même si le garbage collector est tenu de garder les événements "stop-the-world" aussi courts que possible, la capacité du collecteur de bloquer unilatéralement d'autres threads d'interagir avec les références GC permet à d'autres threads de lire et d'écrire des références sans avoir à utiliser des lectures et des écritures verrouillées. Cela peut avoir d'énormes effets sur l'efficacité.
La plupart de ce que .NET VM parvient à accomplir pourrait être fait sans utiliser de machine virtuelle, mais l'utilisation d'un VM offre d'énormes avantages en termes de sécurité et de performances qui dépassent de loin les inconvénients.