Runtime: Führen Sie einen abgestuften JIT ein

Erstellt am 14. Apr. 2016  ·  63Kommentare  ·  Quelle: dotnet/runtime

Warum ist .NET JIT nicht mehrstufig?

Das JIT hat zwei primäre Designziele: Schnelle Startzeit und hoher stationärer Durchsatz.

Diese Ziele erscheinen zunächst widersprüchlich. Aber mit einem zweistufigen JIT-Design sind beide erreichbar:

  1. Der gesamte Code beginnt interpretiert. Dies führt zu einer extrem schnellen Startzeit (schneller als RyuJIT). Beispiel: Die Main -Methode ist fast immer kalt und es ist Zeitverschwendung, sie zu jippen.
  2. Code, der häufig ausgeführt wird, wird mit einem sehr hochwertigen Codegenerator jittert. Sehr wenige Methoden werden heiß sein (1%?). Daher spielt der Durchsatz des hochqualitativen JIT keine große Rolle. Es kann so viel Zeit aufwenden wie ein C-Compiler, um sehr guten Code zu generieren. Außerdem kann es _annehmen_, dass der Code heiß ist. Es kann wie verrückt inline und Loops abrollen. Die Codegröße spielt keine Rolle.

Das Erreichen dieser Architektur scheint nicht zu kostspielig zu sein:

  1. Das Schreiben eines Dolmetschers erscheint billig im Vergleich zu einem JIT.
  2. Ein qualitativ hochwertiger Codegenerator muss erstellt werden. Dies könnte VC oder das LLILC-Projekt sein.
  3. Es muss möglich sein, interpretierten laufenden Code in kompilierten Code zu überführen. Das ist möglich; die JVM macht es. Es wird Stack Replacement (OSR) genannt.

Wird diese Idee vom JIT-Team weiterverfolgt?

.NET läuft auf Hunderten von Millionen Servern. Ich habe das Gefühl, dass viel Leistung auf dem Tisch bleibt und Millionen von Servern wegen suboptimaler Code-Generierung für Kunden verschwendet werden.

Kategorie: Durchsatz
Thema: Big-Wetten
Fähigkeitsniveau : Experte
Kosten: extra groß

area-CodeGen-coreclr enhancement optimization tenet-performance

Hilfreichster Kommentar

Mit aktuellen PRs (https://github.com/dotnet/coreclr/pull/17840, https://github.com/dotnet/sdk/pull/2201) haben Sie auch die Möglichkeit, eine mehrstufige Kompilierung als Laufzeitkonfiguration anzugeben. json-Eigenschaft oder eine msbuild-Projekteigenschaft. Die Verwendung dieser Funktionalität erfordert, dass Sie sich auf sehr aktuellen Builds befinden, während die Umgebungsvariable schon eine Weile existiert.

Alle 63 Kommentare

@GSPP Tiering ist ein ständiges Thema in Planungsgesprächen. Mein Eindruck ist, dass es eine Frage des _wann_ ist, nicht des _ob_, ob das tröstlich ist. Was _warum_ es noch nicht gibt, liegt meiner Meinung nach daran, dass die wahrgenommenen potenziellen Gewinne in der Vergangenheit die zusätzlichen Entwicklungsressourcen nicht rechtfertigten, die erforderlich sind, um die erhöhte Komplexität und das Risiko mehrerer Codegen-Modi zu bewältigen. Ich sollte wirklich die Experten zu diesem Thema sprechen lassen, also füge ich sie hinzu.

/cc @dotnet/jit-contrib @russellhadley

Irgendwie bezweifle ich, dass das in einer Welt von Crossgen/Ngen, Ready to Run und Corert noch relevant ist.

Keines davon liefert derzeit einen hohen Steady-State-Durchsatz, was für die meisten Web-Apps wichtig ist. Wenn sie das jemals tun, bin ich damit zufrieden, da mir persönlich die Startzeit egal ist.

Aber bisher haben alle Codegeneratoren für .NET versucht, einen unmöglichen Spagat zwischen den beiden Zielen zu schaffen, und beide Ziele nicht sehr gut erfüllt. Lassen Sie uns diesen Balanceakt los, damit wir die Optimierungen auf 11 drehen können.

Aber bisher haben alle Codegeneratoren für .NET versucht, einen unmöglichen Spagat zwischen den beiden Zielen zu schaffen, und beide Ziele nicht sehr gut erfüllt. Lassen Sie uns diesen Balanceakt los, damit wir die Optimierungen auf 11 drehen können.

Ich stimme zu, aber um dies zu beheben, sind keine Dinge wie ein Dolmetscher erforderlich. Nur ein guter Crossgen-Compiler, sei es ein besserer RyuJIT oder LLILC.

Ich denke, der größte Vorteil ist für Anwendungen, die zur Laufzeit Code generieren müssen. Dazu gehören dynamische Sprachen und Servercontainer.

Dynamisch generierter Code ist zwar eine Motivation, aber es stimmt auch, dass ein statischer Compiler niemals Zugriff auf alle zur Laufzeit verfügbaren Informationen haben wird. Nicht nur das, selbst wenn er spekuliert (z. B. auf der Grundlage von Profilinformationen), ist es für einen statischen Compiler viel schwieriger, dies in Gegenwart von modalem oder externem kontextabhängigem Verhalten zu tun.

Web-Apps sollten keine Verarbeitung im ngen-Stil benötigen. Es passt nicht gut in die Bereitstellungspipeline. Es braucht viel Zeit, um große Binärdateien zu ngen (auch wenn fast der gesamte Code dynamisch tot oder kalt ist).

Außerdem können Sie sich beim Debuggen und Testen einer Webanwendung nicht darauf verlassen, dass ngen eine realistische Leistung liefert.

Außerdem unterstütze ich Carols Argument, dynamische Informationen zu verwenden. Die Interpretationsstufe kann Profilcode (Zweige, Anzahl der Schleifenfahrten, dynamische Dispatchziele) profilieren. Es ist eine perfekte Übereinstimmung! Erst Profil sammeln, dann optimieren.

Tiering löst alles in jedem Szenario für immer. Ungefähr gesagt :) Dies kann uns tatsächlich zu dem Versprechen von JITs bringen: Erzielen Sie eine Leistung, die _über_ hinausgeht, was ein C-Compiler leisten kann.

Die aktuelle Implementierung von RyuJIT, so wie sie jetzt ist, ist gut genug für ein Tier 1 ... Die Frage ist: Wäre es sinnvoll, ein extremes Tier 2-Optimierungs-JIT für Hot Paths zu haben, das nachträglich ausgeführt werden kann? Im Wesentlichen, wenn wir feststellen oder genügend Laufzeitinformationen haben, um zu wissen, dass etwas heiß ist, oder wenn wir von Anfang an aufgefordert werden, dies stattdessen zu verwenden.

RyuJIT ist bei weitem gut genug, um Tier 1 zu sein. Das Problem dabei ist, dass ein Interpreter eine _weit_ schnellere Startzeit hätte (meiner Einschätzung nach). Das zweite Problem besteht darin, dass der lokale Status der Ausführung des Codes der Stufe 1 auf den neuen Code der Stufe 2 (OSR) übertragbar sein muss, um zur Stufe 2 vorzudringen. Das erfordert RyuJIT-Änderungen. Das Hinzufügen eines Interpreters wäre meiner Meinung nach ein billigerer Weg mit gleichzeitig besserer Startlatenz.

Eine noch günstigere Variante wäre, Running Code nicht durch Tier 2 Code zu ersetzen. Warten Sie stattdessen, bis der Tier-1-Code auf natürliche Weise zurückkehrt. Dies kann ein Problem sein, wenn der Code in eine lange laufende heiße Schleife eintritt. Auf diese Weise wird es niemals die Leistung von Tier 2 erreichen.

Ich denke, das wäre nicht so schlimm und könnte als v1-Strategie verwendet werden. Abschwächende Ideen sind verfügbar, wie z. B. ein Attribut, das eine Methode als heiß kennzeichnet (dies sollte ohnehin auch mit der aktuellen JIT-Strategie vorhanden sein).

@GSPP Das stimmt, aber das bedeutet nicht, dass Sie das beim nächsten Lauf nicht wissen würden. Wenn Jitted-Code und -Instrumentierung persistent werden, erhalten Sie bei der zweiten Ausführung immer noch Tier-2-Code (auf Kosten einiger Startzeit) - was mir persönlich ausnahmsweise egal ist, da ich hauptsächlich Servercode schreibe.

Das Schreiben eines Dolmetschers erscheint billig im Vergleich zu einem JIT.

Anstatt einen brandneuen Interpreter zu schreiben, könnte es sinnvoll sein, RyuJIT mit deaktivierten Optimierungen auszuführen? Würde das die Startzeit genug verbessern?

Ein qualitativ hochwertiger Codegenerator muss erstellt werden. Das könnte VC sein

Sprechen Sie von C2, dem Visual C++-Backend? Das ist nicht plattformübergreifend und nicht Open Source. Ich bezweifle, dass es in absehbarer Zeit passieren wird, beides zu reparieren.

Gute Idee mit dem Deaktivieren von Optimierungen. Das OSR-Problem bleibt jedoch bestehen. Nicht sicher, wie schwierig es ist, Code zu generieren, der es der Laufzeit ermöglicht, den IL-Architekturzustand (Locals und Stack) zur Laufzeit an einem sicheren Punkt abzuleiten, diesen in Jitted-Code der Ebene 2 zu kopieren und die Ausführung der Ebene 2 mitten in der Funktion fortzusetzen. Die JVM macht es, aber wer weiß, wie lange es gedauert hat, das zu implementieren.

Ja, ich sprach von C2. Ich glaube mich zu erinnern, dass mindestens eines der Desktop-JITs auf C2-Code basiert. Funktioniert wahrscheinlich nicht für CoreCLR, aber vielleicht für Desktop. Ich bin sicher, dass Microsoft daran interessiert ist, ausgerichtete Codebasen zu haben, also ist das wahrscheinlich tatsächlich out. LLVM scheint eine gute Wahl zu sein. Ich glaube, dass derzeit mehrere Sprachen daran interessiert sind, dass LLVM mit GCs und mit verwalteten Laufzeiten im Allgemeinen funktioniert.

LLVM scheint eine gute Wahl zu sein. Ich glaube, dass derzeit mehrere Sprachen daran interessiert sind, dass LLVM mit GCs und mit verwalteten Laufzeiten im Allgemeinen funktioniert.

Ein interessanter Artikel zu diesem Thema: Apple hat vor kurzem die letzte Ebene seines JavaScript-JIT von LLVM wegbewegt: https://webkit.org/blog/5852/introducing-the-b3-jit-compiler/ . Wir würden wahrscheinlich auf ähnliche Probleme stoßen wie sie: langsame Kompilierungszeiten und LLVMs mangelnde Kenntnis der Quellsprache.

10x langsamer als RyuJIT wäre für eine 2. Schicht völlig akzeptabel.

Ich glaube nicht, dass die mangelnde Kenntnis der Quellsprache (was ein echtes Problem darstellt) der Architektur von LLVM eigen ist. Ich glaube, dass mehrere Teams damit beschäftigt sind, LLVM in einen Zustand zu versetzen, in dem Quellsprachenkenntnisse einfacher genutzt werden können. _Alle_ Nicht-C-Hochsprachen haben dieses Problem beim Kompilieren auf LLVM.

Das WebKIT FTL/B3-Projekt ist in einer schwierigeren Position als .NET, weil es bei der Ausführung von Code, der _insgesamt_ einige hundert Millisekunden Zeit in Anspruch nimmt und dann beendet wird, hervorragende Leistungen erbringen muss. Dies liegt in der Natur von JavaScript-Arbeitslasten, die Webseiten steuern. .NET ist nicht an dieser Stelle.

@GSPP Ich bin sicher, dass Sie LLILC wahrscheinlich kennen. Wenn nicht, schau mal.

Wir arbeiten seit einiger Zeit an der LLVM-Unterstützung für CLR-Konzepte und haben sowohl in EH- als auch in GC-Verbesserungen investiert. Bei beiden ist noch einiges zu tun. Darüber hinaus gibt es einen unbekannten Arbeitsaufwand, damit Optimierungen in Gegenwart von GC ordnungsgemäß funktionieren.

LLILC scheint ins Stocken geraten zu sein. Ist es?
Am 18. April 2016 um 19:32 Uhr schrieb „Andy Ayers“ [email protected] :

@GSPP https://github.com/GSPP Ich bin sicher, dass Sie LLILC wahrscheinlich kennen
https://github.com/dotnet/llilc. Wenn nicht, schau mal.

Wir arbeiten seit einiger Zeit an der LLVM-Unterstützung für CLR-Konzepte und haben dies getan
sowohl in EH- als auch in GC-Verbesserungen investiert. Es bleibt noch einiges zu tun
beide. Darüber hinaus gibt es einen unbekannten Arbeitsaufwand für Optimierungen
ordnungsgemäß in Gegenwart von GC funktioniert.


Sie erhalten dies, weil Sie kommentiert haben.
Antworten Sie direkt auf diese E-Mail oder zeigen Sie sie auf GitHub an
https://github.com/dotnet/coreclr/issues/4331#issuecomment -211630483

@drbo – LLILC ist im Moment auf Sparflamme – das MS-Team hat sich darauf konzentriert, mehr Ziele in RyuJIT zu erreichen und Probleme zu beheben, die auftreten, wenn CoreCLR zur Veröffentlichung fährt, und das hat so ziemlich unsere ganze Zeit in Anspruch genommen. Es steht auf meiner TODO-Liste (in meiner ausgiebigen Freizeit), einen Beitrag zu den gewonnenen Erkenntnissen zu schreiben, basierend darauf, wie weit wir (derzeit) mit LLILC gekommen sind, aber ich bin noch nicht dazu gekommen.
In Bezug auf Tiering hat dieses Thema im Laufe der Jahre viele Diskussionen ausgelöst. Ich denke, dass wir angesichts einiger der neuen Workloads sowie der neu hinzugefügten versionierbaren, betriebsbereiten Images einen neuen Blick darauf werfen werden, wie und wo gestaffelt werden soll.

@russellhadley hattest du die zeit den post zu schreiben?

Ich nehme an, es sollte etwas an nicht beförderten Stack-Slots und gcroots geben, die die Optimierungen und langsame Jitting-Zeiten brechen ... Ich sollte mir besser den Code des Projekts ansehen.

Ich frage mich auch, ob es möglich und rentabel ist, direkt in SelectionDAG zu springen und einen Teil des LLVM-Backends auszuführen. Zumindest etwas Peephole und Copy Propagation ... wenn zB die gcroot Promotion zu den Registern in LLILC unterstützt wird

Ich bin neugierig auf den Status von LLILC, einschließlich aktueller Engpässe, und wie es gegenüber RyuJIT abschneidet. Da LLVM ein vollwertiger "industrietauglicher" Compiler ist, sollte er eine große Fülle von Optimierungen für OSS haben. Auf der Mailingliste gab es einige Gespräche über eine effizientere, schnellere Serialisierung/Deserialisierung des Bitcode-Formats; Ich frage mich, ob dies eine nützliche Sache für LLILC ist.

Gab es dazu noch weitere Gedanken? @russellhadley CoreCLR wurde veröffentlicht und RyuJIT wurde auf (mindestens) x86 portiert – was steht als nächstes auf der Roadmap?

Siehe dotnet/coreclr#10478 für die Anfänge der Arbeit daran.

Auch dotnet/coreclr#12193

@noahfalk , könnten Sie bitte eine Möglichkeit bereitstellen, der Laufzeitumgebung mitzuteilen, dass sie eine Tier-2-Kompilierung direkt aus dem verwalteten Code selbst erzwingen soll? Die abgestufte Kompilierung ist für die meisten Anwendungsfälle eine sehr gute Idee, aber ich arbeite an einem Projekt, bei dem die Startzeit irrelevant ist, aber der Durchsatz und eine stabile Latenz unerlässlich sind.

Aus dem Kopf heraus könnte dies entweder sein:

  • eine neue Einstellung in der Konfigurationsdatei, ein Schalter wie <gcServer enabled="true" /> , um das JIT zu zwingen, Tier 1 immer zu überspringen
  • oder etwas wie RuntimeHelpers.PrepareMethod , das vom Code für alle Methoden aufgerufen wird, die Teil des Hot Path sind (wir verwenden dies, um unseren Code beim Start vorab zu JITen). Dies hat den Vorteil, dass dem Entwickler, der wissen sollte, was der heiße Weg ist, ein größerer Freiheitsgrad gegeben wird. Eine zusätzliche Überladung dieser Methode wäre völlig in Ordnung.

Zugegeben, nur wenige Projekte würden davon profitieren, aber ich mache mir Sorgen, dass die JIT-Optimierungen standardmäßig übersprungen werden, und ich kann es nicht sagen, dass ich lieber meinen Code stark optimieren soll.

Mir ist bewusst, dass Sie Folgendes in das Designdokument geschrieben haben:

Fügen Sie eine neue Build-Pipeline-Stufe hinzu, auf die über verwaltete Code-APIs zugegriffen werden kann, um selbstmodifizierenden Code auszuführen.

Das klingt sehr interessant 😁, aber ich bin mir nicht ganz sicher, ob es das abdeckt, was ich hier frage.


Auch eine verwandte Frage: Wann würde der zweite JIT-Pass eintreten? Wann wird eine Methode zum n -ten Mal aufgerufen? Wird der JIT in dem Thread ausgeführt, in dem die Methode ausgeführt werden sollte? Wenn dies der Fall ist, würde dies zu einer Verzögerung vor dem Methodenaufruf führen. Wenn Sie aggressivere Optimierungen implementieren, wäre diese Verzögerung länger als die aktuelle JIT-Zeit, was zu einem Problem werden kann.

Es sollte passieren, wenn die Methode oft genug aufgerufen wird, oder wenn es sich um eine Schleife handelt
führt genügend Iterationen aus (Ersetzung auf der Bühne). Es sollte passieren
asynchron in einem Hintergrundthread.

Am 29. Juni 2017, 19:01 Uhr, „Lucas Trzesniewski“ [email protected]
schrieb:

@noahfalk https://github.com/noahfalk , könnten Sie bitte einen Weg finden
um die Laufzeitumgebung anzuweisen, eine Tier-2-Kompilierung sofort zu erzwingen
verwalteter Code selbst? Die abgestufte Zusammenstellung ist für die meisten eine sehr gute Idee
Anwendungsfälle, aber ich arbeite an einem Projekt, bei dem die Startzeit irrelevant ist
aber Durchsatz und eine stabile Latenz sind unerlässlich.

Aus dem Kopf heraus könnte dies entweder sein:

  • eine neue Einstellung in der Konfigurationsdatei, ein Schalter wie enabled="true" />, um den JIT zu zwingen, Tier 1 immer zu überspringen
  • oder so etwas wie RuntimeHelpers.PrepareMethod, was wäre
    wird vom Code für alle Methoden aufgerufen, die Teil des heißen Pfads sind (wir sind
    Verwenden Sie dies, um unseren Code beim Start vorab zu erstellen). Das hat den Vorteil
    Geben Sie dem Entwickler, der was wissen sollte, ein größeres Maß an Freiheit
    der heiße Weg ist. Eine zusätzliche Überladung dieser Methode wäre völlig in Ordnung.

Zugegeben, nur wenige Projekte würden davon profitieren, aber ich mache mir irgendwie Sorgen
die JIT-Optimierungen werden standardmäßig übersprungen, und ich kann es nicht sagen
Ich würde lieber meinen Code stattdessen stark optimieren lassen.

Mir ist bewusst, dass Sie Folgendes in das Designdokument geschrieben haben:

Fügen Sie eine neue Build-Pipeline-Stufe hinzu, auf die über verwaltete Code-APIs zugegriffen werden kann
selbstmodifizierender Code.

Was sehr interessant klingt 😁 aber ich bin mir nicht ganz sicher, was es abdeckt

Ich frage hier.

Auch eine verwandte Frage: Wann würde der zweite JIT-Pass eintreten? Wenn ein
Methode zum n -ten Mal aufgerufen wird? Wird der JIT am stattfinden
der Thread, auf dem die Methode ausgeführt werden sollte? Wenn ja, würde das einführen
Verzögerung vor dem Methodenaufruf. Wenn Sie aggressiver implementieren
Optimierungen wäre diese Verzögerung länger als die aktuelle JIT-Zeit, was
kann zu einem Problem werden.


Sie erhalten dies, weil Sie erwähnt wurden.
Antworten Sie direkt auf diese E-Mail und zeigen Sie sie auf GitHub an
https://github.com/dotnet/coreclr/issues/4331#issuecomment-312130920 ,
oder den Thread stumm schalten
https://github.com/notifications/unsubscribe-auth/AGGWB2WbZ2qVBjRIQWS86MStTSa1ODfoks5sJCzOgaJpZM4IHWs8
.

@ltrzesniewski - Danke für das Feedback! Ich hoffe natürlich, dass die abgestufte Kompilierung für die überwiegende Mehrheit der Projekte nützlich ist, aber die Kompromisse sind möglicherweise nicht für jedes Projekt ideal. Ich habe spekuliert, dass wir eine Umgebungsvariable beibehalten würden, um abgestuftes Jitting zu deaktivieren. In diesem Fall behalten Sie das Laufzeitverhalten, das Sie jetzt haben, mit Jitting in höherer Qualität (aber langsamer zu generieren) im Voraus bei. Ist das Festlegen einer Umgebungsvariablen für Ihre App sinnvoll? Andere Optionen sind ebenfalls möglich, ich tendiere nur zur Umgebungsvariable, weil sie eine der einfachsten Konfigurationsoptionen ist, die wir verwenden können.

Auch eine verwandte Frage: Wann würde der zweite JIT-Pass eintreten?

Dies ist eine Politik, die sich sehr wahrscheinlich im Laufe der Zeit weiterentwickeln wird. Die aktuelle Prototypimplementierung verwendet eine vereinfachte Richtlinie: „Wurde die Methode >= 30 Mal aufgerufen“
https://github.com/dotnet/coreclr/blob/master/src/vm/tieredcompilation.cpp#L89
https://github.com/dotnet/coreclr/blob/master/src/vm/tieredcompilation.cpp#L122

Praktischerweise schlägt diese sehr einfache Richtlinie eine nette Leistungsverbesserung auf meinem Computer vor, auch wenn es nur eine Vermutung ist. Um bessere Richtlinien zu erstellen, müssen wir Feedback zur Nutzung in der realen Welt erhalten, und um dieses Feedback zu erhalten, müssen die Kernmechanismen in einer Vielzahl von Szenarien einigermaßen robust sein. Mein Plan ist es also, zuerst die Robustheit/Kompatibilität zu verbessern und dann weitere Untersuchungen zur Optimierungsrichtlinie durchzuführen.

@DemiMarie - Wir haben derzeit nichts, was Loop-Iterationen als Teil der Richtlinie verfolgt, aber es ist eine interessante Perspektive für die Zukunft.

Gab es irgendwelche Gedanken zu Profiling, spekulativer Optimierung und
Deoptimierung? Die JVM erledigt all dies.

Am 29. Juni 2017 um 20:58 Uhr schrieb „Noah Falk“ [email protected] :

@ltrzesniewski https://github.com/ltrzesniewski - Danke für die
Rückmeldung! Ich hoffe natürlich, dass die abgestufte Zusammenstellung für die Weiten nützlich ist
Mehrheit der Projekte, aber die Kompromisse sind möglicherweise nicht für jedes Projekt ideal.
Ich habe spekuliert, dass wir eine Umgebungsvariable an Ort und Stelle belassen würden
Tiered Jitting deaktivieren, in diesem Fall behalten Sie das Laufzeitverhalten bei
habe jetzt mit höherer qualität (aber langsamer zu generieren) jitting vorne. Ist
Setzen Sie eine Umgebungsvariable etwas Vernünftiges für Ihre App?
Andere Optionen sind auch möglich, ich tendiere nur zur Umgebung
Variable, da dies eine der einfachsten Konfigurationsoptionen ist, die wir verwenden können.

Auch eine verwandte Frage: Wann würde der zweite JIT-Pass eintreten?

Dies ist eine Politik, die sich sehr wahrscheinlich im Laufe der Zeit weiterentwickeln wird. Die jetzige
Die Prototyp-Implementierung verwendet eine vereinfachte Richtlinie: „Ist die Methode gewesen
>= 30 Mal angerufen"
https://github.com/dotnet/coreclr/blob/master/src/vm/
tieredcompilation.cpp#L89
https://github.com/dotnet/coreclr/blob/master/src/vm/
tieredcompilation.cpp#L122

Praktischerweise schlägt diese sehr einfache Richtlinie eine schöne Leistungsverbesserung vor
meine Maschine, auch wenn es nur eine Vermutung ist. Um bessere Richtlinien zu schaffen
Wir müssen Feedback zur Nutzung in der realen Welt erhalten und dieses Feedback erhalten
erfordert, dass die Kernmechanik in einer Vielzahl von einigermaßen robust ist
Szenarien. Mein Plan ist also, zuerst die Robustheit/Kompatibilität zu verbessern und dann zu tun
mehr Exploration für Tuning-Politik.

@DemiMarie https://github.com/demimarie - Wir haben so etwas nicht
verfolgt Loop-Iterationen jetzt als Teil der Richtlinie, aber es ist interessant
Perspektive für die Zukunft.


Sie erhalten dies, weil Sie erwähnt wurden.
Antworten Sie direkt auf diese E-Mail und zeigen Sie sie auf GitHub an
https://github.com/dotnet/coreclr/issues/4331#issuecomment-312146470 ,
oder den Thread stumm schalten
https://github.com/notifications/unsubscribe-auth/AGGWB5m2qCnOKJsaXFCFigI3J6Ql8PMQks5sJEgZgaJpZM4IHWs8
.

@noahfalk Eine Umgebungsvariable ist definitiv keine Lösung, mit der Sie diese Anwendung für Anwendung steuern können. Bei Server-/Dienst-Apps ist es Ihnen normalerweise egal, wie lange es dauert, bis die Anwendung gestartet wird (ich weiß, dass wir dies nicht auf Kosten der Leistung tun). Bei der Entwicklung einer Datenbank-Engine kann ich Ihnen aus erster Hand sagen, dass sie von Anfang an so schnell wie möglich funktionieren muss, und sogar auf ungewöhnlichen Pfaden oder Benchmarks, die von potenziellen neuen Kunden durchgeführt werden.

Da andererseits die Betriebszeit in typischen Umgebungen in Wochen gemessen werden kann, ist es uns egal, ob es auch nur 30 Sekunden dauert; Was uns jedoch interessiert, ist, dass es 10 Schritte rückwärts ist, den Benutzer zu zwingen, einen allgemeinen Schalter (alles oder nichts) auszugeben, oder sich sogar darum kümmern zu müssen (wie dies standardmäßig in den Konfigurationsdateien festgelegt ist).

Verstehen Sie mich nicht falsch, ich freue mich mehr als auf ein Tiered JIT, weil es den Weg für eine hohe Leistung öffnet und so viel Zeit in Anspruch nimmt, wie Sie Codepfad für die Optimierung auf JIT-Ebene benötigen. Ich habe das sogar vor langer Zeit selbst in informellen Gesprächen mit einigen der JIT-Ingenieure vorgeschlagen, und Sie hatten es bereits auf dem Radar. Aber eine Möglichkeit, das Verhalten anwendungsweit (nicht systemweit) anzupassen, ist (zumindest für uns) ein kritischer Qualitätsindikator für diese spezielle Funktion.

EDIT: Einige Stilprobleme.

@redknightlois - Danke für die Nachbereitung

Eine Umgebungsvariable ist definitiv keine Lösung, mit der Sie diese Anwendung für Anwendung steuern können.

Ein wenig verwirrt in diesem Teil ... Umgebungsvariablen haben eine Granularität pro Prozess und nicht pro System, zumindest auf den Plattformen, die mir bekannt waren. Zum Beispiel heute, um die mehrstufige Kompilierung zum Testen in nur einer Anwendung zu aktivieren, die ich ausführe:

set COMPLUS_EXPERIMENTAL_TieredCompilation=1
MyApp.exe
set COMPLUS_EXPERIMENTAL_TieredCompilation=0

was uns wichtig ist, ist, dass [wir nicht] den Benutzer zwingen ... sich darum zu kümmern

Ich nehme an, Sie möchten eine Konfigurationseinstellung, die vom App-Entwickler angegeben werden kann, nicht von der Person, die die App ausführt? Eine Möglichkeit mit der env var besteht darin, dass die App vom Benutzer einen trivialen Wrapper (wie ein Batch-Skript) startet, der die coreclr-App startet, obwohl ich zugeben muss, dass dies etwas unelegant erscheint. Ich bin offen für Alternativen und setze nicht auf die env var. Nur um Erwartungen zu wecken, dies ist kein Bereich, in den ich in naher Zukunft aktive Designanstrengungen investieren werde, aber ich stimme zu, dass eine angemessene Konfiguration wichtig ist, um dorthin zu gelangen.

Auch ein Heads-up - vorausgesetzt, wir gehen den Weg der abgestuften Kompilierung auf anständige Weise weiter, könnte ich mir leicht vorstellen, dass wir einen Punkt erreichen, an dem die Aktivierung der abgestuften Kompilierung nicht nur der schnellste Start ist, sondern auch die aktuelle Steady-State-Leistung übertrifft. Im Moment ist Startleistung mein Ziel, aber es ist nicht die Grenze dessen, was wir damit machen können :)

Gab es irgendwelche Gedanken zu Profiling, spekulativer Optimierung und
Deoptimierung?

@DemiMarie – Sie sind sicherlich in Gesprächen aufgetaucht und ich denke, viele Leute sind begeistert, dass die abgestufte Zusammenstellung diese Möglichkeiten eröffnet. Ich spreche nur für mich selbst, ich versuche, mich darauf zu konzentrieren, die grundlegenden mehrstufigen Compilation-Fähigkeiten bereitzustellen, bevor ich meine Ziele höher setze. Andere Leute in unserer Community führen mich wahrscheinlich bereits bei anderen Anwendungen an.

@noahfalk Ja, unelegant zu sein bedeutet auch, dass der übliche Prozess zum Ausführen fehleranfällig werden kann (und sehr wahrscheinlich wird), und das ist im Wesentlichen das Problem (der einzige Weg, um absolut sicher zu sein, dass niemand es vermasselt, besteht darin, es systemweit zu tun). Eine Alternative, von der wir wissen, dass sie funktioniert, ist, dass Sie auf die gleiche Weise konfigurieren können, ob Sie den Server-GC mit einem Eintrag in app.config verwenden, Sie können dasselbe mit der mehrstufigen Kompilierung tun (zumindest bis zum Tiered kann die Steady-State-Leistung durchweg übertreffen). Als JIT können Sie dies auch pro Baugruppe mit assembly.config tun und würden ein gewisses Maß an Fähigkeiten bieten, die es derzeit nicht gibt, wenn andere Knöpfe auch auf diese Weise ausgewählt werden können.

Umgebungsvariablen werden häufig pro Benutzer oder pro System festgelegt, was möglicherweise negative Auswirkungen auf alle diese Prozesse über mehrere Versionen der Laufzeit hat. Eine Pro-App-Konfigurationsdatei scheint eine viel bessere Lösung zu sein (auch wenn Pro-Benutzer/Pro-System ebenfalls verfügbar ist) – so etwas wie die Desktop-Konfigurationswerte, die in app.config festgelegt werden könnten, aber auch env vars oder Registry verwenden .

Ich denke, wir werden den gängigsten Pfad implementieren, der pro Anwendung ist. Systemweite Einstellungen können auch nützlich sein, aber ich denke nicht, dass wir darüber nachdenken müssen, bevor die Funktion implementiert wird.

Bitte beachten Sie, dass wir noch nicht im Detail ausgearbeitet haben, was der Second-Tier-Jit für die Optimierung tun sollte, obwohl wir einige Ideen haben. Es könnte genau das tun, was der Jit heute tut, aber sehr wahrscheinlich wird es mehr tun.

Lassen Sie mich also auf einige mögliche Komplikationen hinweisen ...

Es ist möglich, dass der Jit der zweiten Ebene sich selbst auf der Grundlage von Beobachtungen bootet, die zum Verhalten des Codes gemacht wurden, der von dem Jit der ersten Ebene erstellt wurde. Das Umgehen des First-Tier-Jit und das direkte Nachfragen nach dem Second-Tier-Jit funktioniert möglicherweise überhaupt nicht oder nicht so gut, als würde man Tiering einfach seinen Lauf lassen. Möglicherweise würde eine "Tiering Bypass"-Option, wie auch immer implementiert, am Ende Code wie den Code geben, den der Jit heute standardmäßig produziert, nicht den Code, den ein Jit der zweiten Ebene produzieren könnte.

Der Jit der zweiten Ebene kann so abgestimmt werden, dass seine Ausführung auf einer großen Menge von Methoden relativ langsame Jit-Zeiten verursacht (da wir davon ausgehen, dass relativ wenige Methoden am Ende mit dem Jit der zweiten Ebene gejittert werden, und wir erwarten der Jit der zweiten Ebene führt eine gründlichere Optimierung durch). Wir kennen hier noch nicht die richtigen Kompromisse.

Davon abgesehen...

Ich denke, ein Methodenattribut für "aggressive Optimierung" ist sinnvoll - eines, das den Jit auffordert, sich in etwa so zu verhalten, wie sich der Jit der zweiten Ebene für bestimmte Methoden verhalten könnte, und vielleicht diese Methoden während des Prejitting überspringt (da Prejit-Code langsamer als Jit-Code ausgeführt wird, speziell für R2R). Aber die Anwendung dieses Konzepts auf eine ganze Assembly oder auf alle Assemblys in einer Anwendung scheint nicht so ansprechend zu sein.

Wenn Sie das, was in nativen Compilern passiert, als geeignete Analogie nehmen, können Kompromisse zwischen Leistung und Kompilierzeit/Codegröße bei höheren Optimierungsstufen ziemlich schlecht werden, z. B. 10x längere Kompilierungen für eine Gesamtleistungsverbesserung von 1-2%. Der Schlüssel zum Rätsel ist zu wissen, welche Methoden wichtig sind, und der einzige Weg, dies zu tun, besteht darin, dass entweder die Programmierer es wissen oder das System es selbst herausfindet.

@AndyAyersMS Ich denke, du hast da den Nagel auf den Kopf getroffen. Das JIT-Behandlungsattribut „aggressive Optimierung“ würde wahrscheinlich die meisten Probleme lösen, wenn es nicht möglich ist, genügend Informationen für das JIT zu haben, um isoliert besseren Code zu produzieren, ohne dass das JIT der ersten Ebene Zeit hat, dieses Feedback zu liefern.

Das Attribut @redknightlois funktioniert nicht, wenn wir mehr Tiering wollen: - T3 JIT, T4 JIT, ... Ich bin mir nicht sicher, ob zwei Ebenen nicht ausreichen, aber wir sollten diese Möglichkeit zumindest in Betracht ziehen.

Es wäre großartig, etwas Ähnliches wie MPGO verwenden zu können, um mit Jitted-Code der zweiten Ebene zu beginnen. Schnelles Vorspulen der ersten Ebene, anstatt sie vollständig zu umgehen.

@AndyAyersMS , hat die Tatsache, dass Azul ein verwaltetes JIT für die JVM mit LLVM implementiert hat, die Integration von LLVM in die CLR vereinfacht? Anscheinend wurden dabei Änderungen in den Upstream von LLVM gepusht.

Nur zu Ihrer Information, ich habe eine Reihe von Arbeitselementen für eine bestimmte Arbeit erstellt, die wir erledigen müssen, um das gestaffelte Jitting in Gang zu bringen (#12609, dotnet/coreclr#12610, dotnet/coreclr#12611, dotnet/coreclr#12612, dotnet/coreclr #12617). Wenn sich Ihr Interesse direkt auf eine davon bezieht, können Sie gerne Ihre Kommentare hinzufügen. Für alle anderen Themen gehe ich davon aus, dass die Diskussion hier bleiben wird, oder jeder kann ein Problem für ein bestimmtes Unterthema erstellen, wenn genügend Interesse besteht, um es für sich allein aufzuteilen.

@MendelMonteiro Das Bereitstellen von Feedback-Daten im MPGO-Stil beim Jitten ist sicherlich eine Option (derzeit können wir diese Daten nur beim Prejitten zurücklesen). Es gibt verschiedene Grenzen für das, was instrumentiert werden kann, daher können nicht alle Methoden auf diese Weise gehandhabt werden, es gibt andere Einschränkungen, die wir berücksichtigen müssen (z Die MPGO-Daten sind für viele Benutzer ein Hindernis, und die MPGO-Daten können mit dem übereinstimmen, was wir beim Bootstrapping von der ersten Ebene hätten, oder auch nicht, aber die Idee hat sicherlich ihren Wert.

Was eine LLVM-basierte Oberschicht angeht – offensichtlich haben wir uns mit LLILC in gewissem Umfang damit befasst, und zu der Zeit standen wir in häufigem Kontakt mit den Azul-Leuten, daher sind wir mit vielen der Dinge vertraut, in denen sie tätig waren LLVM, um es für die Kompilierung von Sprachen mit präziser GC zugänglicher zu machen.

Es gab (und gibt es wahrscheinlich immer noch) signifikante Unterschiede in der LLVM-Unterstützung, die für die CLR benötigt wird, im Vergleich zu der, die für Java benötigt wird, sowohl in GC als auch in EH, und in den Einschränkungen, die man dem Optimierer auferlegen muss. Um nur ein Beispiel zu nennen: Der GC der CLRs kann derzeit keine verwalteten Zeiger tolerieren, die auf das Ende von Objekten zeigen. Java handhabt dies über einen Basis-/abgeleiteten gepaarten Berichtsmechanismus. Wir müssten entweder Unterstützung für diese Art von gepaartem Reporting in die CLR einbinden oder die Optimierer-Passes von LLVM einschränken, um niemals diese Art von Zeigern zu erstellen. Darüber hinaus war der LLILC-Jit langsam und wir waren uns nicht sicher, welche Art von Codequalität er letztendlich produzieren würde.

Daher schien (und scheint) es verfrüht, herauszufinden, wie LLILC in einen potenziellen mehrstufigen Ansatz passen könnte, der noch nicht existierte. Die Idee für den Moment ist, Tiering in das Framework einzufügen und RyuJit für den Jit der zweiten Stufe zu verwenden. Wenn wir mehr lernen, entdecken wir vielleicht, dass es tatsächlich Platz für höherrangige Jits gibt, oder verstehen zumindest besser, was wir sonst noch tun müssen, bevor solche Dinge einen Sinn ergeben.

@AndyAyersMS Vielleicht können Sie die erforderlichen Änderungen auch in LLVM einführen, um seine Einschränkungen zu umgehen.

Funktioniert Multicore JIT und seine Profiloptimierung mit coreclr?

@benaadams - Ja, Multicore-JIT funktioniert. Ich kann mich nicht erinnern, in welchen (falls überhaupt) Szenarien es standardmäßig aktiviert ist, aber Sie können es über die Konfiguration aktivieren: https://github.com/dotnet/coreclr/blob/master/src/inc/clrconfigvalues.h# L548

Ich habe einen halbwegs Spielzeug-Compiler geschrieben und festgestellt, dass die knallharten Optimierungen die meiste Zeit ziemlich gut auf derselben Infrastruktur durchgeführt werden können und nur sehr wenige Dinge im Optimierer der höheren Ebene durchgeführt werden können.

Was ich meine ist Folgendes: Wenn eine Funktion viele Male getroffen wird, sind die Parameter wie folgt:

  • Erhöhen Sie die Anzahl der Inline-Anweisungen
  • Verwenden Sie einen "fortschrittlicheren" Registerzuordner (LLVM-ähnlicher Backtracking-Colorator oder Full-Colorizer).
  • Führen Sie weitere Optimierungsdurchgänge durch, vielleicht einige, die sich auf das lokale Wissen spezialisiert haben. Beispiel: Erlauben Sie das Ersetzen der vollständigen Objektzuweisung durch die Stapelzuweisung, wenn das Objekt in der Methode deklariert und nicht im Hauptteil einer größeren Inline-Funktion zugewiesen wird.
  • Verwenden Sie PIC für die meisten getroffenen Objekte, bei denen CHA nicht möglich ist. Sogar StringBuilder zum Beispiel wird sehr wahrscheinlich nicht überschrieben, der Code könnte, wenn er jedes Mal als markiert ist, wenn er mit einem StringBuilder getroffen wurde, alle Methoden, die innerhalb aufgerufen werden, sicher devirtualisiert werden können und ein Typwächter vor den Zugriff des SB gesetzt wird.

Es wäre auch sehr schön, aber vielleicht wird dadurch mein Traum wach, dass CompilerServices anbieten, den „erweiterten Compiler“ offenzulegen, um über Code oder Metadaten darauf zugreifen zu können, sodass Orte wie Spiele oder Handelsplattformen davon profitieren könnten vorab zusammenstellen, welche Klassen und Methoden "tiefer kompiliert" werden sollen. Dies ist kein NGen, aber wenn ein nicht abgestufter Compiler nicht unbedingt möglich (wünschenswert) ist, zumindest um den schwereren optimierten Code für kritische Teile verwenden zu können, die diese zusätzliche Leistung benötigen. Wenn eine Plattform die starken Optimierungen nicht anbietet (sagen wir Mono), sind die API-Aufrufe natürlich im Grunde ein NO-OP.

Dank der harten Arbeit von @noahfalk , @kouvel und anderen haben wir jetzt eine solide Grundlage für Tiering geschaffen.

Ich schlage vor, dass wir dieses Thema schließen und ein „Wie können wir Tiered Jitting verbessern“-Problem eröffnen. Ich ermutige jeden, der sich für das Thema interessiert, das aktuelle Tiering auszuprobieren, um eine Vorstellung davon zu bekommen, wo die Dinge gerade stehen. Wir würden gerne Feedback zum tatsächlichen Verhalten erhalten, ob gut oder schlecht.

Ist das aktuelle Verhalten irgendwo beschrieben? Ich habe nur das gefunden , aber es geht eher um die Implementierungsdetails als um das Tiering.

Ich glaube, wir werden bald eine Art zusammenfassende Beschreibung mit einigen der Daten haben, die wir gesammelt haben.

Tiering kann in 2.1 aktiviert werden, indem COMPlus_TieredCompilation=1 gesetzt wird. Wenn Sie es versuchen, melden Sie bitte zurück, was Sie finden....

Mit aktuellen PRs (https://github.com/dotnet/coreclr/pull/17840, https://github.com/dotnet/sdk/pull/2201) haben Sie auch die Möglichkeit, eine mehrstufige Kompilierung als Laufzeitkonfiguration anzugeben. json-Eigenschaft oder eine msbuild-Projekteigenschaft. Die Verwendung dieser Funktionalität erfordert, dass Sie sich auf sehr aktuellen Builds befinden, während die Umgebungsvariable schon eine Weile existiert.

Wie wir bereits mit @jkotas besprochen haben, kann Tiered JIT die Startzeit verbessern. Funktioniert es, wenn wir native Bilder verwenden?
Wir haben Messungen für mehrere Apps auf dem Tizen-Telefon durchgeführt und hier sind die Ergebnisse:

System-DLLs|App-DLLs|Tiered|Zeit, s
-----------|--------|------|--------
R2R |R2R |nein |2.68
R2R |R2R |ja |2,61 (-3%)
R2R |nein |nein |4.40
R2R |nein |ja |3,63 (-17%)

Wir werden auch den FNV-Modus überprüfen, aber es sieht so aus, als ob er gut funktioniert, wenn keine Bilder vorhanden sind.

cc @gbalykov @nkaretnikov2

Zu Ihrer Information, die mehrstufige Kompilierung ist jetzt die Standardeinstellung für .NET Core: https://github.com/dotnet/coreclr/pull/19525

@alpencolt , Verbesserungen der Startzeit können geringer sein, wenn AOT-Kompilierung wie R2R verwendet wird. Die Verbesserung der Startzeit ergibt sich derzeit aus schnellerem Jitting mit weniger Optimierungen, und bei Verwendung der AOT-Kompilierung wäre weniger JIT erforderlich. Einige Methoden sind nicht vorgeneriert, z. B. einige Generika, IL-Stubs und andere dynamische Methoden. Einige Generika können vom Tiering während des Starts profitieren, selbst wenn die AOT-Kompilierung verwendet wird.

Ich werde dieses Thema schließen, da ich denke, dass ich mit dem Commit von @kouvel die Frage im Titel erreicht habe: D. Fragen oder besondere Untersuchungen. Wenn jemand denkt, dass es vorzeitig geschlossen ist, lassen Sie es uns natürlich wissen.

@kouvel Es tut uns leid, das geschlossene Problem zu kommentieren. Ich frage mich, ob die Anwendung bei der Verwendung von AOT-Kompilierung wie Crossgen immer noch von der Kompilierung der zweiten Ebene für die Hotspot-Codepfade profitieren wird.

@daxian-dbw ja, sehr sogar; zur Laufzeit kann der Jit Cross-Assembly-Inlining (zwischen DLLs) durchführen; Eliminierung von Verzweigungen basierend auf Laufzeitkonstanten ( readonly static ); etc

@benaadams Und ein gut gestalteter AOT-Compiler könnte das nicht?

Ich habe einige Informationen dazu unter https://blogs.msdn.microsoft.com/dotnet/2018/08/02/tiered-compilation-preview-in-net-core-2-1/ gefunden:

Die vorkompilierten Images haben Versionierungsbeschränkungen und CPU-Anweisungseinschränkungen, die einige Arten der Optimierung verbieten. Für alle Methoden in diesen Bildern, die häufig aufgerufen werden, fordert die mehrstufige Kompilierung das JIT auf, optimierten Code in einem Hintergrundthread zu erstellen, der die vorkompilierte Version ersetzt.

Ja, das ist ein Beispiel für „kein gut gestaltetes AOT“. 😛

Die vorkompilierten Images haben Versionierungsbeschränkungen und CPU-Anweisungseinschränkungen, die einige Arten der Optimierung verbieten.

Eines der Beispiele sind die Methoden, die Hardware intrinsisch verwenden. Der AOT-Compiler (Crossgen) geht einfach von SSE2 als Codegen-Ziel auf x86/x64 aus, sodass alle Methoden, die hardwareinterne Methoden verwenden, von Crossgen abgelehnt und von JIT kompiliert werden, das die zugrunde liegenden Hardwareinformationen kennt.

Und ein gut gestalteter AOT-Compiler könnte das nicht?

Der AOT-Compiler benötigt eine Verbindungszeitoptimierung (für assemblierungsübergreifendes Inlining) und eine profilgeführte Optimierung (für Laufzeitkonstanten). In der Zwischenzeit benötigt der AOT-Compiler zur Build-Zeit für SIMD-Code eine Hardware-Info (wie -mavx2 in gcc/clang).

Eines der Beispiele sind die Methoden, die Hardware intrinsisch verwenden. Der AOT-Compiler (Crossgen) geht einfach von SSE2 als Codegen-Ziel auf x86/x64 aus, sodass alle Methoden, die hardwareinterne Methoden verwenden, von Crossgen abgelehnt und von JIT kompiliert werden, das die zugrunde liegenden Hardwareinformationen kennt.

Warte was? Ich kann hier nicht ganz folgen. Warum sollte der AOT-Compiler die Intrinsics ablehnen?

Und ein gut gestalteter AOT-Compiler könnte das nicht?

Der AOT-Compiler benötigt eine Verbindungszeitoptimierung (für assemblierungsübergreifendes Inlining) und eine profilgeführte Optimierung (für Laufzeitkonstanten). In der Zwischenzeit benötigt der AOT-Compiler zur Build-Zeit für SIMD-Code eine "unterm Strich" Hardware-Info (wie -mavx2 in gcc/clang).

Ja, wie gesagt, "ein gut gestalteter AOT-Compiler". 😁

@masonwheeler anderes Szenario; crossgen ist AoT, das mit dem Jit zusammenarbeitet und das Warten/Patching von DLLs ermöglicht, ohne dass eine vollständige Neukompilierung und Neuverteilung der Anwendung erforderlich ist. Es bietet eine bessere Code-Generierung als Tier0 mit schnellerem Start als Tier1; ist aber nicht plattformneutral.

Tier0, Crossgen und Tier1 arbeiten alle als zusammenhängendes Modell in coreclr zusammen

Für die Inline-Cross-Assemblierung (Nicht-Jit) wäre die Kompilierung einer statisch verknüpften ausführbaren Einzeldatei und die vollständige Neukompilierung und Neuverteilung der Anwendung erforderlich, um alle verwendeten Bibliotheken zu patchen und auf die spezifische Plattform abzuzielen (welche Version von SSE, Avx usw. zu verwenden; niedrigste gemeinsame oder produzierende Version für alle?).

corert unterstützt diese Art der Anwendung.

Jedoch; Bestimmte Arten der Verzweigungseliminierung, die der Jit ausführen kann, würden eine große Menge an zusätzlicher Asm-Erzeugung für die alternativen Pfade erfordern; und das Laufzeit-Patching des richtigen Baums

zB jeder Code mit einer Methode wie (wobei der Tier1 Jit alle if s entfernt)

readonly static _numProcs = Environment.ProcessorCount;

public void DoThing()
{
    if (_numProcs == 1) 
    {
       // Single proc path
    }
    else if (_numProcs == 2) 
    {
       // Two proc path
    }
    else
    {
       // Multi proc path
    }
}

@benaadams

Für die Inline-Cross-Assemblierung (Nicht-Jit) wäre die Kompilierung einer statisch verknüpften ausführbaren Einzeldatei und die vollständige Neukompilierung und Neuverteilung der Anwendung erforderlich, um alle verwendeten Bibliotheken zu patchen und auf die spezifische Plattform abzuzielen (welche Version von SSE, Avx usw. zu verwenden; niedrigste gemeinsame oder produzierende Version für alle?).

Es sollte keine vollständige Neuverteilung der Anwendung erforderlich sein. Schauen Sie sich das ART-Kompilierungssystem von Android an: Sie verteilen die Anwendung als verwalteten Code (Java in ihrem Fall, aber es gelten die gleichen Prinzipien) und der Compiler, der auf dem lokalen System lebt, AOT kompiliert den verwalteten Code in eine superoptimierte native ausführbare Datei.

Wenn Sie eine kleine Bibliothek ändern, ist der gesamte verwaltete Code immer noch vorhanden und Sie müssten nicht alles neu verteilen, sondern nur das Ding mit dem Patch, und dann kann das AOT erneut ausgeführt werden, um eine neue ausführbare Datei zu erstellen. (Offensichtlich bricht hier die Android-Analogie aufgrund des APK-App-Verteilungsmodells von Android zusammen, aber das gilt nicht für die Desktop-/Server-Entwicklung.)

und der Compiler, der auf dem lokalen System lebt, AOT kompiliert den verwalteten Code ...

Das ist das vorherige NGen-Modell, das vom vollständigen Framework verwendet wurde; aber glauben Sie nicht, dass es auch eine einzelne Assembly erstellt hat, die den Framework-Code in den Anwendungscode einbettet? Der Unterschied zwischen zwei Ansätzen wurde in den Bing.com-Läufen auf .NET Core 2.1 hervorgehoben! Blogeintrag

ReadyToRun-Images

Verwaltete Anwendungen können oft eine schlechte Startleistung aufweisen, da Methoden zuerst JIT in Maschinencode kompiliert werden müssen. .NET Framework verfügt über eine Vorkompilierungstechnologie, NGEN. NGEN erfordert jedoch, dass der Vorkompilierungsschritt auf dem Computer erfolgt, auf dem der Code ausgeführt wird. Für Bing würde das NGENing auf Tausenden von Maschinen bedeuten. Dies würde in Verbindung mit einem aggressiven Bereitstellungszyklus zu einer erheblichen Reduzierung der Bereitstellungskapazität führen, da die Anwendung auf den Webserver-Computern vorkompiliert wird. Darüber hinaus erfordert die Ausführung von NGEN Administratorrechte, die in einer Rechenzentrumsumgebung häufig nicht verfügbar sind oder stark geprüft werden. Unter .NET Core ermöglicht das Crossgen-Tool die Vorkompilierung des Codes als Vorbereitstellungsschritt, z. B. im Build-Lab, und die für die Produktion bereitgestellten Images sind betriebsbereit!

@masonwheeler AOT sieht sich in .Net aufgrund der dynamischen Natur eines .Net-Prozesses mit Gegenwind konfrontiert. Beispielsweise können Methodenkörper in .Net jederzeit über einen Profiler geändert werden, Klassen können per Reflektion geladen oder erstellt werden, und neuer Code kann von der Laufzeit nach Bedarf für Dinge wie Interop erstellt werden – so dass interprozedurale Analyseinformationen bestenfalls widerspiegeln ein vorübergehender Zustand eines laufenden Prozesses. Jede prozedurale Analyse oder Optimierung (einschließlich Inlining) in .Net muss zur Laufzeit rückgängig gemacht werden können.

AOT funktioniert am besten, wenn die Menge der Dinge, die sich zwischen AOT-Zeit und Laufzeit ändern können, gering ist und die Auswirkungen solcher Änderungen lokalisiert sind, sodass der umfangreiche Umfang, der für die AOT-Optimierung verfügbar ist, weitgehend Dinge widerspiegelt, die immer wahr sein müssen (oder vielleicht einen kleinen viele Alternativen).

Wenn Sie Mechanismen zum Bewältigen oder Einschränken der dynamischen Natur von .Net-Prozessen einbauen können, kann reines AOT ziemlich gut funktionieren – zum Beispiel berücksichtigt .Net Native die Auswirkungen von Reflektion und Interop und verbietet das Laden von Assemblys, Reflection Emit und ( Ich nehme an) Profil anhängen. Aber es ist nicht einfach.

Es sind einige Arbeiten im Gange, die es uns ermöglichen, den Umfang von Crossgen auf mehrere Assemblys zu erweitern, damit wir alle Kernframeworks (oder alle asp.net-Assemblys) als Bündel AOT kompilieren können. Aber das ist nur praktikabel, weil wir den Jit als Fallback haben, um Codegen zu wiederholen, wenn sich die Dinge ändern.

@AndyAyersMS Ich habe nie geglaubt, dass die .NET-AOT-Lösung aus genau den Gründen, die Sie hier beschreiben, eine "reine AOT-only" -Lösung sein sollte. Es ist sehr wichtig, das JIT zur Verfügung zu haben, um bei Bedarf neuen Code zu erstellen. Aber die Situationen, in denen es gebraucht wird, sind sehr in der Minderheit, und deshalb denke ich, dass die Regel von Anders Hejlsberg für Typensysteme hier gewinnbringend angewendet werden könnte:

Statisch wo möglich, dynamisch wenn nötig.

Von System.Linq.Expressions
public TDelegate Compile(bool PreferredInterpretation);

Funktioniert die gestaffelte Kompilierung weiterhin, wenn PreferredInterpretation wahr ist?

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

bencz picture bencz  ·  3Kommentare

yahorsi picture yahorsi  ·  3Kommentare

chunseoklee picture chunseoklee  ·  3Kommentare

btecu picture btecu  ·  3Kommentare

noahfalk picture noahfalk  ·  3Kommentare