Warum werden virtuelle Maschinen benötigt?

8

Anstatt den Quellcode für das jeweilige Betriebssystem (auf das er abzielt) zu kompilieren, kompilieren Sie ihn einmal und führen ihn überall aus.

Für diese Frage würde ich es VM nennen (zum Beispiel sowohl für Java als auch für .NET). So wird die Ausführung von Programmen so etwas wie

 ------------       ----      ----
| Executable |  -> | VM | -> | OS |
 ------------       ----      ----

Es ist durchaus sinnvoll, dass der Compiler für die jeweilige VM generisch bleibt. Die Implementierung der VM kann jedoch je nach dem Computer, auf dem sie installiert werden soll, variieren (z. B. (* nix, Windows, Mac) x (32 Bit, 64 Bit).

Meine Frage ist, anstatt VM für die jeweiligen Computer zu schreiben, warum ist der Compiler nicht für diesen bestimmten Computer geschrieben? Anstatt die entsprechende VM herunterzuladen, laden Sie den jeweiligen Compiler herunter, und dieser Compiler kümmert sich um den Maschinencode + das Betriebssystem für diesen bestimmten Computer. Endergebnis ist die Ausführung von nativem Code für jeden Computer. Auf jeden Fall müsste jeder Quellcode für diesen bestimmten Computer kompiliert werden, aber heutzutage können uns die automatisierten Systeme und SCM-Builds dabei helfen.

Sind meine Gründe für Verwirrung richtig oder fehlen mir hier einige technische Details?

Bearbeiten:

Tragbarkeit :

Ja, das ist ein Grund, aber ist Portabilität in heutigen automatisierten Systemen ein großes Problem? Wie oft müssen wir uns Sorgen machen, dass wir es nicht für andere Maschinen kompilieren müssen? Ein Code, der für einen nativen Computer kompiliert wurde, würde eine viel bessere Leistung bringen. Nehmen wir zum Beispiel Java, Sie können unter Windows keine Low-Level-Programmierung durchführen und müssen JNI wählen.

Nehmen Sie automatisierte Systeme wie TeamCity / Jenkins oder andere. Wir könnten ein solches automatisiertes System einrichten, bei dem Code, der über die Versionskontrolle übermittelt wird, zur ausführbaren Datei führt.

Em Ae
quelle
5
Sie haben Ihre eigene Frage im ersten Absatz beantwortet
Ratschenfreak
6
Es war nicht neu mit Java. UCSD Pascal (nur für ein Beispiel) tat dasselbe.
Jerry Coffin
2
@EmAe Und ich möchte auf die Ironie hinweisen, dass Sie zwei CI-Build-Systeme ausgewählt haben, die auf der JVM ausgeführt werden.
Andrew T Finnell
1
"Anstatt die jeweilige VM herunterzuladen, laden Sie den jeweiligen Compiler herunter und dieser Compiler kümmert sich um den Maschinencode + das Betriebssystem für diesen bestimmten Computer" - wir haben den Namen C. Zum Beispiel (vor Paketmanagern). Es tut mir leid, aber ich muss abstimmen, um zu schließen.
Großmeister
Jeder Compiler enthält nur eine Folge von virtuellen Maschinen. Sie können die Kompilierung irgendwann stoppen, diese Zwischendarstellung serialisieren und dann den Rest des Compilers als "Virtual Machine Interpreter" bezeichnen. Es wird nichts ändern.
SK-Logik

Antworten:

16

Meine Frage ist, anstatt VM für die jeweiligen Computer zu schreiben, warum wird der Compiler nicht für diesen bestimmten Computer geschrieben?

Denn dann hätten Sie keine tragbare ausführbare Datei mehr. Sie hätten eine ausführbare Datei für jede Plattform. Der Benutzer müsste entweder eine bestimmte ausführbare Datei für seine Plattform herunterladen oder den Code selbst auf seiner spezifischen Plattform kompilieren.

Bei einer VM benötigt jede Plattform nur ihre plattformspezifische VM. Anschließend können Sie dieselbe ausführbare Datei auf jeder Plattform verteilen .

Robert Harvey
quelle
2
Heutige VMs haben eine bessere Leistung, als Sie ihnen zuschreiben. Durch die Verwendung einer VM erhalten Sie alle verwalteten Extras wie ein Typsystem, ein Bibliotheksframework und eine Garbage Collection kostenlos.
Robert Harvey
3
@ Robert: Denken Sie daran, dass nicht der gesamte native Code C oder C ++ ist. Sie schreiben Programme nicht langsam ein, weil sie einheimisch sind und daher "mit vielen Feinheiten auf niedriger Ebene umgehen". Sie schreiben nur langsam Programme, weil sie schlecht gestaltete Sprachen sind, die nicht gut mit den einfachen Dingen umgehen können. Sie können jedoch die Leistung von Delphi auf C-Level mit der gleichen einfachen Entwicklung erzielen, die Sie in verwalteten Sprachen finden.
Mason Wheeler
7
@ Andrew: Die Leute können sagen, dass Java laut einem Benchmark, der einen Algorithmus in einem Einzelfall testet, schnell ist. Aber dann führen Sie ein tatsächliches Java-Programm wie OpenOffice aus, und es dauert 30 Sekunden, um den verdammten Dialog zum Speichern zu öffnen, verglichen mit etwa 1-2 Sekunden bei nativem Code, und sie kommen zu dem Schluss, dass all diese theoretische Geschwindigkeit in der Benchmarks bedeuten nichts; Java ist immer noch langsam.
Mason Wheeler
2
@MasonWheeler Als Sie Ihren Kommentar gelöscht und um einen neuen erweitert haben. Die Zahl der Leute, die mit "Programmieren" in der Java-Sprache durchkommen können, ist erheblich höher, da die Eintrittsbarriere für C und C ++ unerschwinglich ist, also wirklich schlechte Programme. Java ist nicht langsam und anekdotische Beweise für eine schreckliche Anwendungssuite beweisen nichts anderes.
Andrew T Finnell
10
@MasonWheeler: Nur um ein häufiges Missverständnis auszuräumen: OpenOffice ist nicht in Java geschrieben - es ist meistens in C ++. Es benötigt nur Java für einige spezielle Funktionen. Siehe wiki.services.openoffice.org/wiki/Java_and_OpenOffice.org .
Sleske
12

Mit diesen VMs fehlt Ihnen etwas Großes. Sie tun genau das, was Sie sagen, aber automatisch. Es wird als Just-In-Time-Compiler bezeichnet, weshalb .NET (unter Windows) und Java der Geschwindigkeit von nativ kompiliertem C ++ - Code sehr nahe kommen.

Der Java / C # -Quellcode wird in Bytecode konvertiert. Dieser Bytecode wird dann auf dem Computer, auf dem er gerade ausgeführt wird, in Maschinencode kompiliert. Zum größten Teil führt die VM den nativen Code aus, anstatt den Bytecode erneut zu verarbeiten.

Ich habe einiges übersprungen und den Prozess zu stark vereinfacht, aber VMs erledigen eine beträchtliche Menge an Arbeit.

Andrew T Finnell
quelle
Du verstehst meine Frage nicht. Ich habe keine Zweifel an der Leistung von Code, wenn er auf der spezifischen VM ausgeführt wird. Die Frage ist, warum VM verwendet wird, wenn wir einen Compiler zum Kompilieren für einen bestimmten Computer haben können.
Em Ae
2
@EmAe Die VM kompiliert es für den jeweiligen Computer. Es heißt JIT. Ich empfehle Ihnen dringend, sich darüber zu informieren.
Andrew T Finnell
1
Okay, ich bin hier verwirrt. Korrigiere mich und hilf mir. Also kompiliert der Compiler für einen bestimmten Computer, auch wenn er für die Ausführung auf einer VM ausgewählt wurde? Wenn dieser kompilierte Code (z. B. JAVA) auf eine VM unter UNIX / Mac / Ubuntu portiert wird, muss dies erneut kompiliert werden? NEIN. Denn für VM ist der native BYTE-CODE und dieser BYTE-CODE wird dann mit JIT konvertiert. Program -> Compiled to byte code -> JIT for machine -> Execution. Es sind 4 Schritte erforderlich. Ich frage, warum wir diese 4 Schritte verwenden? dann können wirProgram -> Machine specific compiler -> Execute
Em Ae
Aus dem gleichen Grund schreiben Sie Ihren gesamten Code in der Assembly nicht immer wieder neu. Wiederverwendung.
Andrew T Finnell
2
Außerdem sagt niemand, dass Sie diese vier Schritte ausführen müssen. Sie können immer einen AOT-Compiler verwenden. gcc.gnu.org/java
TomJ
7

Wie viele Konzepte in der Informatik bietet die VM eine Abstraktionsschicht. Sie schreiben Ihren Code gegen eine 'Schnittstelle' (Bytecode / Zwischensprache) und die Abstraktionsschicht (die VM) behandelt die Implementierungsdetails (Kompilierung für den Zielcomputer und andere Aufgaben). Die Abstraktionsschicht stellt Ihnen einen Satz zur Verfügung von Diensten, deren Implementierungsdetails Sie nicht mehr benötigen. In diesem Fall müssen Sie sich nicht mehr um die Besonderheiten der zugrunde liegenden Hardware kümmern, da die Abstraktionsschichtdienste (VM) vorhanden sind. Der Kunde profitiert auf ähnliche Weise - er muss die Details seiner Plattform nicht kennen, nur um die Abstraktionsschicht verwenden zu können.

Natürlich gibt es Kompromisse mit jeder Abstraktion. Sie verlieren die Kontrolle über die Details auf der anderen Seite der Abstraktion und müssen sich auf diese Abstraktion verlassen, um eine sinnvolle Implementierung zu verwenden. Sie müssen die potenziellen Gewinne durch die Verwendung einer Abstraktion gegen die Kompromisse berücksichtigen. In bestimmten Anwendungen können die Nachteile die Vorteile abwägen - möglicherweise benötigen Sie ein hohes Maß an Kontrolle für jede einzelne Plattform.

Ihr Vorschlag, ein automatisiertes System für Kompilierungen auf verschiedenen Plattformen zu verwenden, ist genau das, was die .NET- und Java-Abstraktionsschichten bereits tun. Einer der Vorteile der JIT-Kompilierung besteht darin, dass der Compiler über spezifische Details zu dem Computer verfügt, auf dem er ausgeführt wird. Dies kann zu Optimierungspotenzialen führen, die sonst bei der Durchführung eines Release-Builds auf dem Computer eines Entwicklers nicht möglich wären.

Wenn Sie Bedenken haben, dass die Laufzeit aufgrund der nativen Codegenerierung und der gemeinsamen Programmausführung langsamer wird, haben Sie die Möglichkeit, den nativen Code während der Installation und nicht bei der ersten Ausführung des Programms zu generieren. Zu diesem Zweck bietet .NET nGen an, mit dem native Codegenerierung zur Installationszeit und nicht zur Laufzeit durchgeführt werden kann. Die CLR verwendet dann den zwischengespeicherten nativen Code, anstatt eine JIT-Kompilierung durchzuführen.

medkg15
quelle
3

Sie vereinfachen dies sehr, plattformübergreifend ist mehr als nur das Kompilieren für einen bestimmten nativen Befehlssatz. Häufig kann derselbe C / C ++ - Code nicht einfach auf verschiedenen Plattformen neu kompiliert werden, da die APIs und Bibliotheken unterschiedlich sind. Beispielsweise unterscheidet sich die GUI-Entwicklung unter Windows und Ubuntu Linux erheblich. Die Socket-Kommunikation ist unter Unix und IBM z / OS usw. wahrscheinlich nicht identisch.

Um also "einmal schreiben, überall kompilieren (und verknüpfen)" zu erreichen, müssten Sie eine Art Abstraktionsschicht erstellen , in der Sie eine gemeinsame API für alle Dienste auf Betriebssystemebene erstellen. Dann müssen Sie dies für alle Plattformen implementieren und verteilen, die Sie unterstützen möchten.

Auch wenn Speichermodelle heutzutage immer ähnlicher werden, gibt es immer noch einige Unterschiede zwischen verschiedenen Plattformen, sodass Sie auch die Speicherzuordnung abstrahieren müssen. Am einfachsten ist es hier, einen eigenen "virtuellen" Speichermanager über dem nativen zu erstellen.

Wenn Sie schon dabei sind, werden Dateisysteme und Zugriffssteuerung (Access Control, ACL) zwischen Unix und Windows ziemlich unterschiedlich behandelt, sodass Sie auch diese Dinge abstrahieren müssen. Möglicherweise erstellen Sie sogar einen eigenen "Datei" -Wrapper für zugrunde liegende Implementierungen.

Dann haben wir Threading. Da Linux nur Prozesse und Windows Threads hat, müssen wir dies auch irgendwie abstrahieren.

Nun, zu diesem Zeitpunkt haben Sie so ziemlich eine virtuelle Maschine gebaut. Der Punkt hier ist, dass das Kompilieren von beliebigem Code für verschiedene Plattformen ziemlich einfach ist, das Erstellen von funktionierender Software jeder Komplexität, die auf jeder Plattform ausgeführt werden kann, jedoch alles andere als trivial ist.

Brei
quelle
2

Ein weiterer großer Vorteil des VM / JIT-Ansatzes ist die Binärkompatibilität. Angenommen, Sie haben eine Assembly / JAR / DLL, die Klasse A enthält, und eine andere Assembly, die Klasse B enthält, die von Klasse A abgeleitet ist. Was passiert, wenn Sie Klasse A ändern, z. B. ein privates Mitglied hinzufügen? Das Hinzufügen eines privaten Mitglieds sollte keinen Einfluss auf Klasse B haben, aber wenn die Assembly, die B enthält, bereits zu nativem Code kompiliert wurde, würden Sie wahrscheinlich seltsame Fehler und schwer zu reproduzierende Abstürze bekommen, da der native Code mit kompiliert wurde das alte Speicherlayout von A, so dass z. B. zu wenig Speicher für die Variablen von A reserviert würde, sodass die Mitglieder von A und die hinzugefügten Mitglieder B denselben Speicherorten zugeordnet würden.

Wenn Sie dagegen eine VM verwenden, verschwinden alle diese Probleme einfach, da beim Kompilieren des Codes zu nativem Code das Layout aller Klassen bekannt ist.

Nikie
quelle
2

Meine Frage ist, anstatt VM für die jeweiligen Computer zu schreiben, warum wird der Compiler nicht für diesen bestimmten Computer geschrieben?

Stellen wir uns für eine Sekunde vor, dass dies so funktioniert. Ich schreibe eine Java-Anwendung, die ich dann kompiliere - der Compiler generiert dann eine ausführbare Datei für jede unterstützte Plattform. Ich habe jetzt die folgenden ausführbaren Dateien:

  • Solaris x64
  • Solaris x86
  • Solaris SPARC
  • Windows x86
  • Windows x64
  • Linux x86
  • Linux x64
  • Mac OS
  • BSD

etc etc. etc.

Ich lade dann alle diese auf meine Website hoch und stelle jedem einen Download-Link zur Verfügung. Ich reiche all diese dann bei download.com, tucows.com, sofpedia.com ein und reiche sie einzeln ein.

Das scheint mir ein großer Schmerz zu sein! Wenn ich die Software auf Version 1.1 aktualisiere, muss ich diesen Vorgang wiederholen.

Und was passiert, wenn eine neue Plattform entsteht? Jetzt ist meine Software für diese Plattform nicht verfügbar, es sei denn, ich aktualisiere meine Entwicklersoftware, um sie zu unterstützen, kompiliere sie neu und veröffentliche meine Software für diese neue Plattform erneut.

Und was ist mit dem Benutzer? Sie besuchen meine Website und müssen dann aus einer Liste genau die richtige Version meiner Software auswählen, die sie für ihre Plattform verwenden möchten. Die meisten Benutzer wissen nicht, ob sie x86 oder x64 ausführen, daher werden sie wahrscheinlich die falsche Version herunterladen und eine Art Fehler erhalten. Was ist, wenn sie dann von Windows auf einen Mac wechseln, müssen sie jetzt zurückgehen und meine Software erneut herunterladen.

All dies ist sowohl für den Entwickler als auch für den Benutzer ein großes Problem, und all dies kann vermieden werden, indem nur ein Bytecode kompiliert und dann über eine VM ausgeführt wird, genau wie dies derzeit bei Java der Fall ist.

Wenn die Leistung fast identisch mit der Ausführung von nativem Code ist, stellt sich nicht so sehr die Frage, warum nicht mit nativem Code kompiliert wird, sondern warum die Kompilierung mit nativem Code durchgeführt wird.

Gavin Coates
quelle
1

Bei der Bearbeitung Ihres Beitrags stellen Sie die Nützlichkeit der Portabilität für VMs in Frage.

In meinem Berufsleben war Portabilität der wichtigste Faktor. Meine Bereitstellungsplattform ist sehr selten dieselbe Plattform, auf der ich sie bereitstelle. Daher kann ich unter Windows entwickeln und testen und an meine QS-Abteilung weitergeben, die unter Linux testen würde und in unserer Produktionsumgebung unter Solaris bereitstellen.

Dies ist nicht nur ein isoliertes und erfundenes Beispiel - dies war wahrscheinlich in den letzten 12 Jahren meiner mehrjährigen Karriere das A und O meiner beruflichen Existenz. Ich bin sicher, es gibt viele andere mit den gleichen oder ähnlichen Erfahrungen.

Wenn wir verschiedene (Cross-) Compiler (auf demselben Quellcode) verwendet haben, um verschiedene Binärdateien zu erstellen, können Sie sich nicht auf die Qualität jeder Binärdatei verlassen, wenn diese nicht auf einem eigenen Betriebssystem / einer eigenen Architektur getestet wurden.

Das wunderbare Open-Source-FreePascal-Compiler-Projekt hat den Slogan "Einmal schreiben, überall kompilieren" - obwohl dies zutrifft, würde ich nicht erwarten, dass seriöse Softwarehäuser dies tun und die Anwendungen ohne gründliche Tests auf allen Plattformen veröffentlichen.

Crollster
quelle
Ich würde immer noch die Gültigkeit des Testens auf einer Plattform und der Bereitstellung auf einer anderen in Frage stellen, selbst wenn eine VM verwendet wird. Sie stellen auf zwei verschiedenen VM-Implementierungen bereit. Eine enthält möglicherweise einen systemspezifischen Fehler, der in der anderen nicht vorhanden ist. Die Chancen sind gering, aber es ist immer noch eine gute Praxis, auf identischen Systemen zu testen und bereitzustellen.
Gavin Coates
TBH, ich stimme tatsächlich zu, aber ist es einfacher, eine VM zu testen als einen Compiler? (dh ist eine VM zuverlässiger, da sie einfacher zu testen ist, oder ist sie genauso riskant?) Zumindest bei VMs wissen Sie, dass der Code von Tausenden (Millionen?) anderer verwendet wurde, während nur Sie (und a Handvoll anderer) haben Ihre Bewerbung zusammengestellt.
Crollster
Crollster: Ich stimme zu. In der realen Welt würden Sie identische Hardware und Software verwenden, aber dies ist manchmal nicht möglich. Die Verwendung einer anderen Plattform mag in den meisten Fällen akzeptabel sein, es ist jedoch zu beachten, dass das Fehlerrisiko besteht, wenn auch relativ gering.
Gavin Coates
1

Einfach: Testen.

Wenn der Code auf der virtuellen Maschine einwandfrei funktioniert, können Sie sicher sein, dass er auf anderer Hardware ausgeführt wird, auf der nachweislich eine funktionierende VM vorhanden ist.

Wenn Sie für verschiedene Plattformen kompilieren, müssen Sie viele Tests durchführen, da jede Plattform ihre eigenen Macken hat.

Pieter B.
quelle
0

Portabilität ist mehr als nur das Kompilieren des Codes!

Das Verhalten eines C-Programms kann auf verschiedenen Plattformen unterschiedlich sein:

int  i;
char c[6];
int * p;

p = &c[2];
p = p + 1;

Funktioniert ohne Beanstandung auf einem IBM Power-Chip, jedoch mit einer Ausnahme auf einem SPARC-Chip.

Die Registrierungseinstellungen für Betriebssystem, Betriebssystemversion und Dateisystemumgebungsvariablen können sich alle auf die Ausführung eines kompilierten Programms auswirken.

Eine JVM garantiert so ziemlich das gleiche Verhalten unabhängig von der Hardware- und Softwareumgebung.

James Anderson
quelle