Ein wiederkehrendes Thema zu SE, das mir in vielen Fragen aufgefallen ist, ist das anhaltende Argument, dass C ++ schneller und / oder effizienter ist als übergeordnete Sprachen wie Java. Das Gegenargument ist, dass moderne JVM oder CLR dank JIT und so weiter für eine wachsende Anzahl von Aufgaben genauso effizient sein können und dass C ++ nur dann effizienter ist, wenn Sie wissen, was Sie tun und warum Sie die Dinge auf eine bestimmte Weise tun wird Leistungssteigerungen verdienen. Das liegt auf der Hand und ist durchaus sinnvoll.
Ich möchte eine grundlegende Erklärung wissen (wenn es so etwas gibt ...), warum und wie bestimmte Aufgaben in C ++ schneller sind als in der JVM oder CLR? Liegt es einfach daran, dass C ++ in Maschinencode kompiliert wird, während die JVM oder CLR zur Laufzeit noch den Verarbeitungsaufwand für die JIT-Kompilierung haben?
Wenn ich versuche, das Thema zu recherchieren, finde ich nur die gleichen Argumente, die ich oben skizziert habe, ohne detaillierte Informationen darüber zu haben, wie C ++ genau für Hochleistungscomputer verwendet werden kann.
quelle
Antworten:
Es geht nur um die Erinnerung (nicht um die JIT). Der JIT-Vorteil gegenüber C besteht hauptsächlich darin, virtuelle oder nicht virtuelle Anrufe durch Inlining zu optimieren, woran der CPU-BTB bereits hart arbeitet.
In modernen Computern ist der Zugriff auf den Arbeitsspeicher sehr langsam (im Vergleich zu allem, was die CPU tut), was bedeutet, dass Anwendungen, die den Cache so oft wie möglich nutzen (was einfacher ist, wenn weniger Arbeitsspeicher verwendet wird), bis zu hundertmal schneller sein können als solche nicht. Es gibt viele Möglichkeiten, wie Java mehr Speicher als C ++ verwendet und es schwieriger macht, Anwendungen zu schreiben, die den Cache voll ausnutzen:
Einige andere Faktoren, die sich auf den Arbeitsspeicher beziehen, jedoch nicht auf den Cache:
Einige dieser Dinge sind Kompromisse (manuelle Speicherverwaltung ist es für die meisten Menschen wert, viel Leistung aufzugeben ), andere sind wahrscheinlich das Ergebnis des Versuchs, Java einfach zu halten, und andere sind Designfehler (wenn auch möglicherweise nur im Nachhinein) (UTF-16 war nämlich eine Kodierung mit fester Länge, als Java erstellt wurde, was die Entscheidung, es auszuwählen, viel verständlicher macht).
Es ist erwähnenswert, dass viele dieser Kompromisse für Java / JVM sehr unterschiedlich sind als für C # / CIL. Die .NET-CIL enthält Strukturen vom Referenztyp, Stapelzuweisung / -übergabe, gepackte Arrays von Strukturen und typinstanziierte Generika.
quelle
Teilweise, aber im Allgemeinen, unter der Annahme eines absolut fantastischen JIT-Compilers auf dem neuesten Stand der Technik, kann richtiger C ++ - Code aus ZWEI Hauptgründen immer noch eine bessere Leistung als Java-Code erbringen:
1) C ++ Vorlagen bieten bessere Möglichkeiten für das Schreiben von Code , das sowohl allgemeine und effizient . Templates bieten dem C ++ - Programmierer eine sehr nützliche Abstraktion mit ZERO-Laufzeitaufwand. (Templates sind im Prinzip Enten-Typisierungen zur Kompilierungszeit.) Im Gegensatz dazu erhalten Sie mit Java-Generika im Grunde genommen nur virtuelle Funktionen. Virtuelle Funktionen haben immer einen Laufzeit-Overhead und können im Allgemeinen nicht eingebunden werden.
In den meisten Sprachen, einschließlich Java, C # und sogar C, können Sie zwischen Effizienz und Allgemeingültigkeit / Abstraktion wählen. C ++ - Vorlagen bieten Ihnen beides (auf Kosten längerer Kompilierzeiten).
2) Die Tatsache, dass der C ++ - Standard nicht viel über das binäre Layout eines kompilierten C ++ - Programms zu sagen hat, gibt C ++ - Compilern viel mehr Spielraum als einem Java-Compiler und ermöglicht bessere Optimierungen (was manchmal zu größeren Schwierigkeiten beim Debuggen führt). ) Tatsächlich erzwingt die Natur der Java-Sprachspezifikation in bestimmten Bereichen eine Leistungsbeeinträchtigung. Sie können beispielsweise kein zusammenhängendes Array von Objekten in Java haben. Sie können nur ein zusammenhängendes Array von Objektzeigern haben(Referenzen), was bedeutet, dass das Iterieren über ein Array in Java immer die Kosten für die Indirektion verursacht. Die Wertesemantik von C ++ ermöglicht jedoch zusammenhängende Arrays. Ein weiterer Unterschied ist die Tatsache, dass C ++ das Zuweisen von Objekten auf dem Stapel ermöglicht, während Java dies nicht zulässt. In der Praxis sind die Zuweisungskosten häufig nahe Null, da die meisten C ++ - Programme dazu neigen, Objekte auf dem Stapel zuzuweisen.
Ein Bereich, in dem C ++ möglicherweise hinter Java zurückbleibt, ist eine Situation, in der viele kleine Objekte auf dem Heap zugeordnet werden müssen. In diesem Fall führt das Garbage Collection-System von Java wahrscheinlich zu einer besseren Leistung als Standard
new
unddelete
in C ++, da die Java-GC die Freigabe von Massendaten ermöglicht. Wiederum kann ein C ++ - Programmierer dies durch die Verwendung eines Speicherpools oder eines Plattenzuordners ausgleichen, wohingegen ein Java-Programmierer keinen Rückgriff hat, wenn er mit einem Speicherzuordnungsmuster konfrontiert wird, für das die Java-Laufzeit nicht optimiert ist.Weitere Informationen zu diesem Thema finden Sie in dieser hervorragenden Antwort .
quelle
std::vector<int>
um ein dynamisches Array, das nur für Ints entwickelt wurde, und der Compiler kann es entsprechend optimieren. AC #List<int>
ist immer noch nur einList
.List<int>
verwendet eineint[]
, nichtObject[]
wie Java. Siehe stackoverflow.com/questions/116988/…vector<N>
wo für den speziellen Fallvector<4>
meinerWas die anderen Antworten (bisher 6) vergessen zu haben scheinen zu erwähnen, aber was ich für sehr wichtig halte, um darauf zu antworten, ist eine der sehr grundlegenden Design-Philosophien von C ++, die Stroustrup vom ersten Tag an formuliert und angewendet hat:
Sie zahlen nicht für das, was Sie nicht nutzen.
Es gibt einige andere wichtige zugrunde liegende Designprinzipien, die C ++ stark geprägt haben (so dass Sie nicht zu einem bestimmten Paradigma gezwungen werden sollten), aber Sie zahlen nicht für das, was Sie nicht verwenden, und das ist genau dort, wo es am wichtigsten ist.
In seinem Buch The Design and Evolution of C ++ (normalerweise als [D & E] bezeichnet) beschreibt Stroustrup, welche Bedürfnisse er hatte, die ihn überhaupt zu C ++ veranlassten. In meinen eigenen Worten: Für seine Doktorarbeit (etwas mit Netzwerksimulationen zu tun, IIRC) implementierte er ein System in SIMULA, das ihm sehr gut gefiel, weil die Sprache es ihm sehr gut ermöglichte, seine Gedanken direkt im Code auszudrücken. Das resultierende Programm lief jedoch viel zu langsam, und um einen Abschluss zu bekommen, schrieb er das Ding in BCPL, einem Vorgänger von C. Den Code in BCPL zu schreiben, den er als Schmerz beschreibt, aber das resultierende Programm war schnell genug, um zu liefern Ergebnisse, die es ihm ermöglichten, seine Promotion zu beenden.
Danach wünschte er sich eine Sprache, mit der reale Probleme so direkt wie möglich in Code übersetzt werden können, der Code aber auch sehr effizient sein kann.
Daraufhin schuf er das, was später zu C ++ wurde.
Das oben angeführte Ziel ist also nicht nur eines von mehreren grundlegenden Konstruktionsprinzipien, es liegt auch sehr nahe am Sinn und Zweck von C ++. Und es ist fast überall in der Sprache zu finden: Funktionen sind nur
virtual
dann verfügbar, wenn Sie dies möchten (da das Aufrufen von virtuellen Funktionen mit einem geringen Aufwand verbunden ist). PODs werden nur dann automatisch initialisiert, wenn Sie dies ausdrücklich anfordern. Ausnahmen kosten Sie nur dann Leistung, wenn Sie dies tatsächlich tun werfen sie (während es ein explizites Designziel war, die Einrichtung / Bereinigung von Stackframes sehr billig zu machen), kein GC läuft, wann immer es sich anfühlt, etc.C ++ hat sich ausdrücklich dafür entschieden, Ihnen einige Vorteile zu ersparen ("muss ich diese Methode hier virtuell machen?"), Um die Leistung zu verbessern ("nein, das tue ich nicht, und jetzt kann der Compiler dies tun
inline
und das Heck aus dem heraus optimieren Ganzes! "), und es überrascht nicht, dass dies tatsächlich zu Leistungsgewinnen im Vergleich zu Sprachen führte, die praktischer sind.quelle
Kennen Sie das Google Research Paper zu diesem Thema?
Aus dem Fazit:
Dies ist zumindest teilweise eine Erklärung im Sinne von "weil C ++ - Compiler in der realen Welt durch empirische Maßnahmen schneller Code produzieren als Java-Compiler".
quelle
Dies ist kein Duplikat Ihrer Fragen, aber die akzeptierte Antwort beantwortet die meisten Ihrer Fragen: Eine moderne Überprüfung von Java
Um zusammenzufassen:
Je nachdem, mit welcher anderen Sprache Sie C ++ vergleichen, erhalten Sie möglicherweise die gleiche Antwort oder nicht.
In C ++ haben Sie:
Dies sind die Merkmale oder Nebenwirkungen der Sprachdefinition, die sie theoretisch speicher- und geschwindigkeitstechnisch effizienter machen als jede andere Sprache, die:
Das aggressive Inlining des Compilers in C ++ reduziert oder eliminiert viele Indirektionen. Die Fähigkeit, kleine Mengen von kompakten Daten zu generieren, macht den Cache benutzerfreundlich, wenn Sie diese Daten nicht über den gesamten Speicher verteilen, sondern zusammen packen (beides ist möglich, C ++ lässt Sie nur wählen). RAII macht das C ++ - Speicherverhalten vorhersehbar und beseitigt viele Probleme bei Echtzeit- oder Halb-Echtzeitsimulationen, die hohe Geschwindigkeit erfordern. Lokalitätsprobleme lassen sich im Allgemeinen so zusammenfassen: Je kleiner das Programm / die Daten, desto schneller die Ausführung. C ++ bietet verschiedene Möglichkeiten, um sicherzustellen, dass Ihre Daten dort sind, wo Sie sie haben möchten (in einem Pool, einem Array oder was auch immer) und dass sie kompakt sind.
Offensichtlich gibt es andere Sprachen, die das Gleiche tun können, aber sie sind weniger beliebt, da sie nicht so viele Abstraktionswerkzeuge wie C ++ bieten und daher in vielen Fällen weniger nützlich sind.
quelle
Es geht hauptsächlich um Speicher (wie Michael Borgwardt sagte) mit ein bisschen JIT-Ineffizienz.
Eine Sache, die nicht erwähnt wird, ist der Cache - um den Cache vollständig zu nutzen, müssen Ihre Daten zusammenhängend angeordnet sein (dh alle zusammen). Mit einem GC-System wird nun Speicher auf dem GC-Heap zugewiesen, was sehr schnell geht. Wenn jedoch Speicher belegt wird, wird der GC regelmäßig aktiv und entfernt nicht mehr benötigte Blöcke und komprimiert die verbleibenden Blöcke. Abgesehen von der offensichtlichen Verlangsamung beim Verschieben der verwendeten Blöcke bedeutet dies, dass die von Ihnen verwendeten Daten möglicherweise nicht zusammengehalten werden. Wenn Sie ein Array mit 1000 Elementen haben, werden diese über den gesamten Heap verteilt, es sei denn, Sie haben sie alle auf einmal zugewiesen (und dann ihren Inhalt aktualisiert, anstatt sie zu löschen und neue zu erstellen, die am Ende des Heaps erstellt werden). Dies erfordert mehrere Speichertreffer, um sie alle in den CPU-Cache zu lesen. Die AC / C ++ - App wird höchstwahrscheinlich den Speicher für diese Elemente reservieren und dann die Blöcke mit den Daten aktualisieren. (Okay, es gibt Datenstrukturen wie eine Liste, die sich eher wie die GC-Speicherzuweisungen verhalten, aber die Leute wissen, dass diese langsamer als Vektoren sind.)
Sie können dies in Betrieb sehen, indem Sie einfach alle StringBuilder-Objekte durch String ersetzen ... Stringbuilder arbeiten, indem sie Speicher vorab zuweisen und füllen. Dies ist ein bekannter Leistungstrick für Java / .NET-Systeme.
Vergessen Sie nicht, dass das Paradigma "Löschen alter und Zuweisen neuer Kopien" in Java / C # sehr häufig verwendet wird, nur weil den Leuten gesagt wird, dass die Speicherzuweisungen aufgrund der GC sehr schnell sind und das Streuspeichermodell daher überall verwendet wird ( mit ausnahme von stringbuildern natürlich), daher verschwenden all deine bibliotheken viel speicher und verbrauchen viel speicher, wovon keine den vorteil der zusammenhängend- keit hat. Beschuldige den Hype um GC dafür - sie sagten dir, dass die Erinnerung frei sei, lol.
Der GC selbst ist offensichtlich ein weiterer Perfektionstreffer - wenn er ausgeführt wird, muss er nicht nur durch den Heap fegen, sondern auch alle nicht verwendeten Blöcke freigeben und dann alle Finalisierer ausführen (obwohl dies früher separat durchgeführt wurde) nächstes Mal mit angehaltener App) (Ich weiß nicht, ob es noch so ein Volltreffer ist, aber alle Dokumente, die ich lese, verwenden nur Finalisierer, wenn es wirklich nötig ist) und dann muss es diese Blöcke in Position bringen, damit der Haufen ist komprimiert, und aktualisieren Sie den Verweis auf die neue Position des Blocks. Sie sehen, es ist viel Arbeit!
Perf-Treffer für C ++ - Speicher sind auf die Speicherzuweisung zurückzuführen. Wenn Sie einen neuen Block benötigen, müssen Sie den Heap nach dem nächsten freien Speicherplatz durchsuchen, der groß genug ist. Bei einem stark fragmentierten Heap ist dies bei weitem nicht so schnell wie bei einem GC "Ordnen Sie am Ende einfach einen weiteren Block zu", aber ich denke, dies ist nicht so langsam wie die gesamte Arbeit, die die GC-Komprimierung leistet, und kann durch die Verwendung mehrerer Blockheaps mit fester Größe (auch als Speicherpools bezeichnet) verringert werden.
Es gibt noch mehr ... wie das Laden von Assemblys aus dem GAC, das eine Sicherheitsüberprüfung erfordert, Prüfpfade (schalten Sie sxstrace ein und schauen Sie sich nur an, was gerade los ist !) Und allgemeine andere Überentwicklungen, die bei Java / .net viel beliebter zu sein scheinen als C / C ++.
quelle
"Liegt es einfach daran, dass C ++ in Assembly- / Maschinencode kompiliert wird, während Java / C # zur Laufzeit noch den Verarbeitungsaufwand für die JIT-Kompilierung hat?" Grundsätzlich ja!
Kurz gesagt, Java hat mehr Overhead als nur die JIT-Kompilierung. Zum Beispiel erledigt es viel mehr Überprüfungen für Sie (so erledigt es Dinge wie
ArrayIndexOutOfBoundsExceptions
undNullPointerExceptions
). Der Müllsammler ist ein weiterer erheblicher Aufwand.Es ist ein ziemlich detaillierter Vergleich hier .
quelle
Beachten Sie, dass im Folgenden nur der Unterschied zwischen nativer und JIT-Kompilierung verglichen wird und die Besonderheiten einer bestimmten Sprache oder eines bestimmten Frameworks nicht behandelt werden. Es kann legitime Gründe dafür geben, eine bestimmte Plattform darüber hinaus zu wählen.
Wenn wir behaupten, dass nativer Code schneller ist, sprechen wir über den typischen Anwendungsfall von nativ kompiliertem Code im Vergleich zu JIT-kompiliertem Code, bei dem die typische Verwendung einer JIT-kompilierten Anwendung vom Benutzer ausgeführt werden soll, mit sofortigen Ergebnissen (z. B. Nr warte zuerst auf den Compiler). In diesem Fall glaube ich nicht, dass irgendjemand behaupten kann, dass mit JIT kompilierter Code mit nativem Code übereinstimmen oder ihn übertreffen kann.
Nehmen wir an, wir haben ein Programm in einer Sprache X geschrieben und können es mit einem nativen Compiler und erneut mit einem JIT-Compiler kompilieren. Jeder Arbeitsablauf hat die gleichen Phasen, die verallgemeinert werden können als (Code -> Zwischendarstellung -> Maschinencode -> Ausführung). Der große Unterschied zwischen zwei ist, welche Stufen vom Benutzer und welche vom Programmierer gesehen werden. Bei der nativen Kompilierung sieht der Programmierer bis auf die Ausführungsstufe alles, aber bei der JIT-Lösung sieht der Benutzer neben der Ausführung auch die Kompilierung des Maschinencodes.
Die Behauptung, dass A schneller als B ist, bezieht sich auf die Zeit, die das Programm benötigt, um ausgeführt zu werden, wie es vom Benutzer gesehen wird . Wenn wir davon ausgehen, dass beide Codeteile in der Ausführungsphase identisch ausgeführt werden, müssen wir davon ausgehen, dass der JIT-Workflow für den Benutzer langsamer ist, da er auch den Zeitpunkt T der Kompilierung zum Maschinencode sehen muss, bei dem T> 0 ist Damit der JIT-Arbeitsablauf den nativen Arbeitsablauf für den Benutzer ausführen kann, muss die Ausführungszeit des Codes verringert werden, sodass die Ausführung + Kompilierung zum Maschinencode niedriger ist als nur die Ausführungsphase des nativen Arbeitsablaufs. Dies bedeutet, dass wir den Code in der JIT-Kompilierung besser optimieren müssen als in der nativen Kompilierung.
Dies ist jedoch ziemlich undurchführbar, da zur Durchführung der notwendigen Optimierungen zur Beschleunigung der Ausführung mehr Zeit für die Kompilierung des Maschinencodes aufgewendet werden muss und somit jede Zeit, die wir durch den optimierten Code einsparen, verloren geht Wir fügen es der Zusammenstellung hinzu. Mit anderen Worten, die "Langsamkeit" einer JIT-basierten Lösung beruht nicht nur auf der zusätzlichen Zeit für die JIT-Kompilierung, sondern der durch diese Kompilierung erzeugte Code ist langsamer als eine native Lösung.
Ich werde ein Beispiel verwenden: Registerzuordnung. Da der Speicherzugriff einige tausend Mal langsamer ist als der Registerzugriff, möchten wir nach Möglichkeit Register verwenden und haben so wenig Speicherzugriff wie möglich, aber wir haben eine begrenzte Anzahl von Registern, und wir müssen den Status in den Speicher verschieben, wenn wir ihn benötigen ein Register. Wenn wir einen Registerzuweisungsalgorithmus verwenden, dessen Berechnung 200 ms dauert, und dadurch 2 ms Ausführungszeit einsparen, wird die Zeit für einen JIT-Compiler nicht optimal genutzt. Lösungen wie Chaitins Algorithmus, mit dem sich hochoptimierter Code erzeugen lässt, sind ungeeignet.
Die Rolle des JIT-Compilers besteht darin, die beste Balance zwischen Kompilierungszeit und Qualität des produzierten Codes zu finden, wobei jedoch die schnelle Kompilierungszeit eine große Rolle spielt, da Sie den Benutzer nicht warten lassen möchten. Die Leistung des ausgeführten Codes ist im JIT-Fall langsamer, da der native Compiler bei der Optimierung des Codes nicht an die Zeit gebunden ist und daher die besten Algorithmen verwenden kann. Die Möglichkeit, dass die Gesamtkompilierung + Ausführung für einen JIT-Compiler nur die Ausführungszeit für nativ kompilierten Code überschreiten kann, ist effektiv 0.
Unsere VMs beschränken sich jedoch nicht nur auf die JIT-Kompilierung. Sie verwenden zeitnahe Kompilierungstechniken, Caching, Hot Swapping und adaptive Optimierungen. Ändern wir also unsere Behauptung, dass die Leistung dem Benutzer entspricht, und beschränken Sie sie auf die Zeit, die für die Ausführung des Programms benötigt wird (vorausgesetzt, wir haben AOT kompiliert). Wir können den ausführenden Code effektiv dem nativen Compiler (oder besser?) Gleichsetzen. Ein großer Vorteil für VMs ist, dass sie möglicherweise Code mit einer besseren Qualität als ein nativer Compiler produzieren können, da sie Zugriff auf mehr Informationen haben - die des laufenden Prozesses, beispielsweise wie oft eine bestimmte Funktion ausgeführt werden kann. Die VM kann dann über Hot-Swapping adaptive Optimierungen auf den wichtigsten Code anwenden.
Es gibt jedoch ein Problem mit diesem Argument - es wird davon ausgegangen, dass profilgesteuerte Optimierung und dergleichen nur für VMs gilt, was jedoch nicht zutrifft. Wir können es auch auf die native Kompilierung anwenden - indem wir unsere Anwendung mit aktivierter Profilerstellung kompilieren, die Informationen aufzeichnen und dann die Anwendung mit diesem Profil neu kompilieren. Es ist wahrscheinlich auch erwähnenswert, dass Code-Hot-Swapping nicht nur von einem JIT-Compiler ausgeführt werden kann, sondern auch von systemeigenem Code - obwohl die JIT-basierten Lösungen dafür leichter verfügbar sind und den Entwickler erheblich entlasten. Die große Frage ist also: Kann uns eine VM einige Informationen bieten, die die native Kompilierung nicht kann, was die Leistung unseres Codes steigern kann?
Ich kann es selbst nicht sehen. Wir können die meisten Techniken einer typischen VM auch auf systemeigenen Code anwenden - obwohl der Prozess aufwändiger ist. Ebenso können wir Optimierungen eines nativen Compilers auf eine VM zurück anwenden, die AOT-Kompilierung oder adaptive Optimierungen verwendet. Die Realität ist, dass der Unterschied zwischen nativ ausgeführtem Code und dem, der in einer VM ausgeführt wird, nicht so groß ist, wie wir angenommen haben. Sie führen letztendlich zum gleichen Ergebnis, verfolgen jedoch einen anderen Ansatz, um dorthin zu gelangen. Die VM verwendet einen iterativen Ansatz, um optimierten Code zu erstellen, den der native Compiler von Anfang an erwartet (und der durch einen iterativen Ansatz verbessert werden kann).
Ein C ++ - Programmierer argumentiert möglicherweise, dass er die Optimierungen von Anfang an benötigt, und sollte nicht darauf warten, dass eine VM herausfindet, wie sie dies tun soll, wenn überhaupt. Dies ist bei unserer aktuellen Technologie wahrscheinlich ein gültiger Punkt, da der aktuelle Grad an Optimierungen in unseren VMs unter dem liegt, den native Compiler bieten können. Dies ist jedoch möglicherweise nicht immer der Fall, wenn sich die AOT-Lösungen in unseren VMs verbessern usw.
quelle
Dieser Artikel ist eine Zusammenfassung einer Reihe von Blog-Beiträgen, die versuchen, die Geschwindigkeit von c ++ mit der von c # zu vergleichen, und die Probleme, die Sie in beiden Sprachen lösen müssen, um leistungsstarken Code zu erhalten. Die Zusammenfassung lautet: "Ihre Bibliothek ist weitaus wichtiger als alles andere, aber wenn Sie in C ++ sind, können Sie das überwinden." oder "moderne Sprachen haben bessere Bibliotheken und erzielen so mit geringerem Aufwand schnellere Ergebnisse", abhängig von Ihrer philosophischen Neigung.
quelle
Ich denke, dass die eigentliche Frage hier nicht lautet: "Was ist schneller?" aber "welches hat das beste potenzial für mehr leistung?" Unter diesen Gesichtspunkten setzt sich C ++ klar durch - es ist zu nativem Code kompiliert, es gibt kein JITting, es ist eine niedrigere Abstraktionsebene usw.
Das ist noch lange nicht alles.
Da C ++ kompiliert wird, müssen alle Compileroptimierungen zur Kompilierungszeit durchgeführt werden, und Compileroptimierungen, die für einen Computer geeignet sind, können für einen anderen Computer völlig falsch sein. Es ist auch der Fall, dass globale Compiler-Optimierungen bestimmte Algorithmen oder Codemuster anderen vorziehen können und werden.
Andererseits wird ein JITted-Programm zur JIT-Zeit optimiert, sodass es einige Tricks abrufen kann, die ein vorkompiliertes Programm nicht kann, und sehr spezifische Optimierungen für den Computer vornehmen kann, auf dem es tatsächlich ausgeführt wird, und für den Code, auf dem es tatsächlich ausgeführt wird. Sobald Sie den anfänglichen Overhead der JIT überwunden haben, besteht in einigen Fällen die Möglichkeit, dass Sie schneller sind.
In beiden Fällen wird eine vernünftige Implementierung des Algorithmus und andere Fälle, in denen der Programmierer nicht dumm ist, wahrscheinlich weitaus bedeutendere Faktoren sein - zum Beispiel ist es durchaus möglich, in C ++ einen vollständig hirntoten String-Code zu schreiben, der von Even umhüllt wird eine interpretierte Skriptsprache.
quelle
-march=native
) kompilieren . - "Es ist eine niedrigere Abstraktionsebene" ist nicht wirklich wahr. C ++ verwendet genau so übergeordnete Abstraktionen wie Java (oder sogar höhere: funktionale Programmierung? Template-Metaprogrammierung?), Implementiert jedoch die Abstraktionen weniger "sauber" als Java.Die JIT-Kompilierung wirkt sich tatsächlich negativ auf die Leistung aus. Wenn Sie einen "perfekten" Compiler und einen "perfekten" JIT-Compiler entwerfen, gewinnt die erste Option immer an Leistung.
Sowohl Java als auch C # werden in Zwischensprachen interpretiert und dann zur Laufzeit zu nativem Code kompiliert, wodurch die Leistung verringert wird.
Aber jetzt ist der Unterschied für C # nicht so offensichtlich: Microsoft CLR erzeugt unterschiedlichen nativen Code für unterschiedliche CPUs, wodurch der Code für den Computer, auf dem er ausgeführt wird, effizienter wird, was nicht immer von C ++ - Compilern durchgeführt wird.
PS C # ist sehr effizient geschrieben und hat nicht viele Abstraktionsebenen. Dies gilt nicht für Java, das nicht so effizient ist. In diesem Fall zeigen C # -Programme mit ihrer großartigen CLR häufig eine bessere Leistung als C ++ - Programme. Weitere Informationen zu .Net und CLR finden Sie in Jeffrey Richters "CLR via C #" .
quelle