Heim > Java > javaLernprogramm > Hauptteil

Leistungssteigerung durch statische Analyse, Bildinitialisierung und Heap-Snapshotting

PHPz
Freigeben: 2024-09-03 10:30:59
Original
222 Leute haben es durchsucht

Von monolithischen Strukturen bis zur Welt verteilter Systeme hat die Anwendungsentwicklung einen langen Weg zurückgelegt. Die massive Einführung von Cloud Computing und Microservice-Architektur hat die Herangehensweise an die Erstellung und Bereitstellung von Serveranwendungen erheblich verändert. Anstelle riesiger Anwendungsserver verfügen wir jetzt über unabhängige, individuell bereitgestellte Dienste, die sofort in Aktion treten
nach Bedarf.

Ein neuer Spieler im Block, der dieses reibungslose Funktionieren beeinträchtigen kann, könnte jedoch „Kaltstarts“ sein. Kaltstarts treten ein, wenn die erste Anfrage für einen frisch erzeugten Arbeiter verarbeitet wird. Diese Situation erfordert eine Initialisierung der Sprachlaufzeit und der Dienstkonfiguration, bevor die eigentliche Anfrage verarbeitet wird. Die mit Kaltstarts verbundene Unvorhersehbarkeit und langsamere Ausführung kann zu einem Verstoß gegen die Service Level Agreements eines Cloud-Dienstes führen. Wie kann man also dieser wachsenden Besorgnis begegnen?

Native Image: Optimierung der Startzeit und des Speicherbedarfs

Um die Ineffizienzen von Kaltstarts zu bekämpfen, wurde ein neuartiger Ansatz entwickelt, der Point-to-Analyse, Anwendungsinitialisierung zur Erstellungszeit, Heap-Snapshotting und vorzeitige (AOT)-Kompilierung umfasst. Diese Methode basiert auf der Annahme einer geschlossenen Welt und erfordert, dass alle Java-Klassen zum Zeitpunkt der Erstellung vorbestimmt und zugänglich sind. In dieser Phase ermittelt eine umfassende Points-to-Analyse alle erreichbaren Programmelemente (Klassen, Methoden, Felder), um sicherzustellen, dass nur wesentliche Java-Methoden kompiliert werden.

Der Initialisierungscode für die Anwendung kann während des Build-Prozesses und nicht zur Laufzeit ausgeführt werden. Dies ermöglicht die Vorabzuordnung von Java-Objekten und den Aufbau komplexer Datenstrukturen, die dann zur Laufzeit über einen „Image-Heap“ zur Verfügung gestellt werden. Dieser Image-Heap ist in die ausführbare Datei integriert und sorgt so für sofortige Verfügbarkeit beim Start der Anwendung. Die
Die iterative Ausführung der Point-to-Analyse und des Snapshots wird fortgesetzt, bis ein stabiler Zustand (Fixpunkt) erreicht ist, wodurch sowohl die Startzeit als auch der Ressourcenverbrauch optimiert werden.

Detaillierter Arbeitsablauf

Die Eingabe für unser System ist Java-Bytecode, der aus Sprachen wie Java, Scala oder Kotlin stammen könnte. Der Prozess behandelt die Anwendung, ihre Bibliotheken, das JDK und die VM-Komponenten einheitlich, um eine native ausführbare Datei zu erstellen, die für ein Betriebssystem und eine Architektur spezifisch ist – ein sogenanntes „natives Image“. Der Erstellungsprozess umfasst iterative Points-to-Analysen und Heap-Snapshots, bis ein fester Punkt erreicht ist, sodass die Anwendung über registrierte Rückrufe aktiv teilnehmen kann. Diese Schritte werden zusammenfassend als nativer Image-Erstellungsprozess bezeichnet (Abbildung 1)

Enhancing Performance with Static Analysis, Image Initialization and Heap Snapshotting

Abbildung 1 – Native Image-Erstellungsprozess (Quelle: redhat.com)

Point-to-Analyse

Wir verwenden eine Points-to-Analyse, um die Erreichbarkeit von Klassen, Methoden und Feldern zur Laufzeit zu ermitteln. Die Punkt-zu-Analyse beginnt mit allen Einstiegspunkten, wie etwa der Hauptmethode der Anwendung, und durchläuft iterativ alle transitiv erreichbaren Methoden, bis ein fester Punkt erreicht ist (Abbildung 2).

Enhancing Performance with Static Analysis, Image Initialization and Heap Snapshotting

Abbildung 2 – Punkte zur Analyse

Unsere Point-to-Analyse nutzt das Frontend unseres Compilers, um Java-Bytecode in die High-Level-Zwischendarstellung des Compilers (IR) zu analysieren. Anschließend wird die IR in einen Typ-Fluss-Graphen umgewandelt. In diesem Diagramm stellen Knoten Anweisungen dar, die auf Objekttypen wirken, während Kanten gerichtete Nutzungskanten zwischen Knoten bezeichnen, die von der Definition auf die Verwendung verweisen. Jeder Knoten verwaltet einen Typstatus, der aus einer Liste von Typen besteht, die den Knoten erreichen können, und Nullheitsinformationen. Typzustände breiten sich über die Nutzungskanten aus; Wenn sich der Typstatus eines Knotens ändert, wird diese Änderung an alle Verwendungen weitergegeben. Wichtig ist, dass Typzustände nur erweitert werden können. Neue Typen können zu einem Typstatus hinzugefügt werden, bestehende Typen werden jedoch niemals entfernt. Dieser Mechanismus stellt sicher, dass die
Die Analyse konvergiert letztendlich zu einem festen Punkt und führt zur Beendigung.

Führen Sie den Initialisierungscode aus

Die Points-to-Analyse steuert die Ausführung des Initialisierungscodes, wenn dieser einen lokalen Fixpunkt erreicht. Dieser Code hat seinen Ursprung in zwei separaten Quellen: Klasseninitialisierern und benutzerdefiniertem Code-Batch, der zur Build-Zeit über eine Funktionsschnittstelle ausgeführt wird:

  1. Klasseninitialisierer: Jede Java-Klasse kann einen Klasseninitialisierer haben, der durch eine -Methode angegeben wird, die statische Felder initialisiert. Entwickler können wählen, welche Klassen zur Build-Zeit oder zur Laufzeit initialisiert werden sollen.

  2. Explizite Rückrufe: Entwickler können benutzerdefinierten Code über von unserem System bereitgestellte Hooks implementieren und vor, während oder nach den Analysephasen ausführen.

Hier sind die APIs, die für die Integration in unser System bereitgestellt werden.

Passive API (fragt den aktuellen Analysestatus ab)

boolean isReachable(Class<?> clazz);

boolean isReachable(Field field);

boolean isReachable(Executable method);
Nach dem Login kopieren

Weitere Informationen finden Sie im QueryReachabilityAccess

Aktive API (registriert Rückrufe für Analysestatusänderungen):

void registerReachabilityHandler(Consumer<DuringAnalysisAccess> callback, Object... elements);

void registerSubtypeReachabilityHandler(BiConsumer<DuringAnalysisAccess, Class<?>> callback, Class<?> baseClass);

void registerMethodOverrideReachabilityHandler(BiConsumer<DuringAnalysisAccess, Executable> callback, Executable baseMethod);
Nach dem Login kopieren

Weitere Informationen finden Sie im BeforeAnalysisAccess

Während dieser Phase kann die Anwendung benutzerdefinierten Code wie Objektzuweisung und Initialisierung größerer Datenstrukturen ausführen. Wichtig ist, dass der Initialisierungscode auf den aktuellen Points-to-Analysestatus zugreifen kann und so Abfragen hinsichtlich der Erreichbarkeit von Typen, Methoden oder Feldern ermöglicht. Dies wird mithilfe der verschiedenen isReachable()-Methoden erreicht, die von WhileAnalysisAccess bereitgestellt werden. Mithilfe dieser Informationen kann die Anwendung Datenstrukturen erstellen, die für die erreichbaren Segmente der Anwendung optimiert sind.

Heap-Snapshotting

Schließlich erstellt das Heap-Snapshotting einen Objektgraphen, indem es Stammzeigern wie statischen Feldern folgt, um eine umfassende Ansicht aller erreichbaren Objekte zu erstellen. Dieses Diagramm füllt dann die
des nativen Bildes aus Image-Heap, um sicherzustellen, dass der Anfangszustand der Anwendung beim Start effizient geladen wird.

Um den transitiven Abschluss erreichbarer Objekte zu erzeugen, durchläuft der Algorithmus Objektfelder und liest ihre Werte mithilfe von Reflektion. Es ist wichtig zu beachten, dass der Image Builder in der Java-Umgebung ausgeführt wird. Bei diesem Durchlauf werden nur Instanzfelder berücksichtigt, die von der Punkt-zu-Analyse als „gelesen“ markiert wurden. Wenn eine Klasse beispielsweise über zwei Instanzfelder verfügt, eines jedoch nicht als gelesen markiert ist, wird das über das nicht markierte Feld erreichbare Objekt aus dem Bildheap ausgeschlossen.

Wenn ein Feldwert auftritt, dessen Klasse zuvor nicht durch die Points-to-Analyse identifiziert wurde, wird die Klasse als Feldtyp registriert. Diese Registrierung stellt sicher, dass in nachfolgenden Iterationen der Point-to-Analyse der neue Typ an alle Feldlesevorgänge und transitiven Verwendungen im Typflussdiagramm weitergegeben wird.

Der folgende Codeausschnitt beschreibt den Kernalgorithmus für Heap-Snapshots:

Declare List worklist := []
Declare Set reachableObjects := []

Function BuildHeapSnapshot(PointsToState pointsToState)
For Each field in pointsToState.getReachableStaticObjectFields()
Call AddObjectToWorkList(field.readValue())
End For

    For Each method in pointsToState.getReachableMethods()
        For Each constant in method.embeddedConstants()
            Call AddObjectToWorkList(constant)
        End For
    End For

    While worklist.isNotEmpty
        Object current := Pop from worklist
        If current Object is an Array
            For Each value in current
                Call AddObjectToWorkList(value)
                Add current.getClass() to pointsToState.getObjectArrayTypes()
            End For
        Else
            For Each field in pointsToState.getReachableInstanceObjectFields(current.getClass())
                Object value := field.read(current)
                Call AddObjectToWorkList(value)
                Add value.getClass() to pointsToState.getFieldValueTypes(field)
            End For
        End If
    End While
    Return reachableObjects
End Function
Nach dem Login kopieren

Zusammenfassend lässt sich sagen, dass der Heap-Snapshotting-Algorithmus effizient einen Snapshot des Heaps erstellt, indem er erreichbare Objekte und ihre Felder systematisch durchläuft. Dadurch wird sichergestellt, dass nur relevante Objekte im Bildheap enthalten sind, wodurch die Leistung und der Speicherbedarf des nativen Bildes optimiert werden.

Abschluss

Zusammenfassend lässt sich sagen, dass der Prozess des Heap-Snapshottings eine entscheidende Rolle bei der Erstellung nativer Images spielt. Durch das systematische Durchlaufen erreichbarer Objekte und ihrer Felder erstellt der Heap-Snapshotting-Algorithmus einen Objektgraphen, der den transitiven Abschluss erreichbarer Objekte aus Stammzeigern wie statischen Feldern darstellt. Dieser Objektgraph wird dann als Bild-Heap in das native Bild eingebettet und dient als erster Heap beim Start des nativen Bildes.

Während des gesamten Prozesses verlässt sich der Algorithmus auf den Status der Points-to-Analyse, um zu bestimmen, welche Objekte und Felder für die Aufnahme in den Bild-Heap relevant sind. Objekte und Felder, die von der Punkt-zu-Analyse als „gelesen“ markiert wurden, werden berücksichtigt, während nicht markierte Entitäten ausgeschlossen werden. Darüber hinaus registriert der Algorithmus beim Auftreffen auf bisher unbekannte Typen diese zur Weitergabe in nachfolgenden Iterationen der Points-to-Analyse.

Insgesamt optimiert das Heap-Snapshotting die Leistung und Speichernutzung nativer Bilder, indem es sicherstellt, dass nur notwendige Objekte im Bild-Heap enthalten sind. Dieser systematische Ansatz erhöht die Effizienz und Zuverlässigkeit der nativen Bildausführung.

Das obige ist der detaillierte Inhalt vonLeistungssteigerung durch statische Analyse, Bildinitialisierung und Heap-Snapshotting. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Quelle:dev.to
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage
Über uns Haftungsausschluss Sitemap
Chinesische PHP-Website:Online-PHP-Schulung für das Gemeinwohl,Helfen Sie PHP-Lernenden, sich schnell weiterzuentwickeln!