Verbesserungen bei Android 8.0 ART

Die Android-Runtime (ART) wurde in der Version Android 8.0 erheblich verbessert. Die folgende Liste fasst die Verbesserungen zusammen, die Gerätehersteller in ART erwarten können.

Gleichzeitig komprimierender Garbage Collector

Wie auf der Google I/O angekündigt, verfügt ART in Android 8.0 über einen neuen gleichzeitig komprimierenden Garbage Collector (GC). Dieser Kollektor komprimiert den Heap jedes Mal, wenn GC ausgeführt wird, und während die App ausgeführt wird, mit nur einer kurzen Pause für die Verarbeitung von Thread-Roots. Hier sind seine Vorteile:

  • GC komprimiert den Heap immer: durchschnittlich 32 % kleinere Heap-Größen im Vergleich zu Android 7.0.
  • Die Komprimierung ermöglicht die Zuweisung von Thread-lokalen Bump-Pointer-Objekten: Zuweisungen sind 70 % schneller als in Android 7.0.
  • Bietet 85 % kürzere Pausenzeiten für den H2-Benchmark im Vergleich zu Android 7.0 GC.
  • Pausenzeiten skalieren nicht mehr mit der Heap-Größe; Apps sollten in der Lage sein, große Heaps zu verwenden, ohne sich Gedanken über Fehler machen zu müssen.
  • Details zur GC-Implementierung – Lesebarrieren:
    • Lesebarrieren sind ein kleiner Arbeitsaufwand für jedes gelesene Objektfeld.
    • Diese werden im Compiler optimiert, können jedoch einige Anwendungsfälle verlangsamen.

Schleifenoptimierungen

ART setzt in der Version Android 8.0 eine Vielzahl von Schleifenoptimierungen ein:

  • Grenzen prüfen Eliminierungen
    • Statisch: Bereiche liegen zur Kompilierungszeit nachweislich innerhalb der Grenzen
    • Dynamisch: Laufzeittests stellen sicher, dass Schleifen innerhalb der Grenzen bleiben (andernfalls deaktivieren)
  • Eliminierung von Induktionsvariablen
    • Entfernen Sie die tote Induktion
    • Ersetzen Sie die Induktion, die erst nach der Schleife verwendet wird, durch Ausdrücke in geschlossener Form
  • Eliminierung toten Codes innerhalb des Schleifenkörpers, Entfernung ganzer Schleifen, die tot werden
  • Kraftabbau
  • Schleifentransformationen: Umkehrung, Vertauschung, Aufteilung, Entrollen, unimodular usw.
  • SIMDisierung (auch Vektorisierung genannt)

Der Schleifenoptimierer befindet sich in einem eigenen Optimierungsdurchlauf im ART-Compiler. Die meisten Schleifenoptimierungen ähneln Optimierungen und Vereinfachungen an anderer Stelle. Bei einigen Optimierungen, die das CFG auf eine überdurchschnittlich aufwändige Art und Weise neu schreiben, ergeben sich Herausforderungen, da sich die meisten CFG-Dienstprogramme (siehe nodes.h) auf die Erstellung eines CFG konzentrieren und nicht auf das Umschreiben eines solchen.

Analyse der Klassenhierarchie

ART in Android 8.0 verwendet Class Hierarchy Analysis (CHA), eine Compiler-Optimierung, die virtuelle Aufrufe basierend auf den durch die Analyse von Klassenhierarchien generierten Informationen in direkte Aufrufe devirtualisiert. Virtuelle Aufrufe sind teuer, da sie rund um eine Vtable-Suche implementiert werden und einige abhängige Ladevorgänge erfordern. Auch virtuelle Anrufe können nicht inline sein.

Hier ist eine Zusammenfassung der damit verbundenen Verbesserungen:

  • Dynamische Statusaktualisierung der Einzelimplementierungsmethode – Am Ende der Klassenverknüpfungszeit, wenn die vtable gefüllt wurde, führt ART einen Eintrag für Eintrag mit der vtable der Superklasse durch.
  • Compiler-Optimierung – Der Compiler nutzt die Einzelimplementierungsinformationen einer Methode. Wenn für eine Methode A.foo das Single-Implementation-Flag gesetzt ist, devirtualisiert der Compiler den virtuellen Aufruf in einen direkten Aufruf und versucht als Ergebnis weiter, den direkten Aufruf zu inline.
  • Ungültigmachung des kompilierten Codes – Auch am Ende der Klassenverknüpfungszeit, wenn Informationen zur Einzelimplementierung aktualisiert werden, wenn Methode A.foo, die zuvor eine Einzelimplementierung hatte, dieser Status aber jetzt ungültig ist, der gesamte kompilierte Code, der von der Annahme abhängt, dass Methode A. foo hat eine Einzelimplementierung, deren kompilierter Code ungültig gemacht werden muss.
  • Deoptimierung – Für live kompilierten Code, der sich auf dem Stapel befindet, wird eine Deoptimierung eingeleitet, um den ungültig gemachten kompilierten Code in den Interpretermodus zu zwingen, um die Korrektheit zu gewährleisten. Es wird ein neuer Deoptimierungsmechanismus verwendet, der eine Mischung aus synchroner und asynchroner Deoptimierung ist.

Inline-Caches in .oat-Dateien

ART verwendet jetzt Inline-Caches und optimiert die Aufrufstellen, für die genügend Daten vorhanden sind. Die Inline-Caches-Funktion zeichnet zusätzliche Laufzeitinformationen in Profilen auf und nutzt sie, um dynamische Optimierungen zur Vorabkompilierung hinzuzufügen.

Dexlayout

Dexlayout ist eine in Android 8.0 eingeführte Bibliothek, um Dex-Dateien zu analysieren und sie entsprechend einem Profil neu anzuordnen. Dexlayout zielt darauf ab, Laufzeitprofilinformationen zu verwenden, um Abschnitte der Dex-Datei während der Kompilierung im Leerlauf auf dem Gerät neu anzuordnen. Durch die Gruppierung von Teilen der Dex-Datei, auf die häufig gemeinsam zugegriffen wird, können Programme durch verbesserte Lokalität bessere Speicherzugriffsmuster aufweisen, RAM sparen und die Startzeit verkürzen.

Da Profilinformationen derzeit erst verfügbar sind, nachdem Apps ausgeführt wurden, ist dexlayout während der Leerlaufwartung in die Kompilierung auf dem Gerät von dex2oat integriert.

Dex-Cache-Entfernung

Bis Android 7.0 besaß das DexCache-Objekt vier große Arrays, proportional zur Anzahl bestimmter Elemente in der DexFile, nämlich:

  • Zeichenfolgen (eine Referenz pro DexFile::StringId),
  • Typen (eine Referenz pro DexFile::TypeId),
  • Methoden (ein nativer Zeiger pro DexFile::MethodId),
  • Felder (ein nativer Zeiger pro DexFile::FieldId).

Diese Arrays wurden zum schnellen Abrufen von Objekten verwendet, die wir zuvor aufgelöst haben. In Android 8.0 wurden alle Arrays mit Ausnahme des Methodenarrays entfernt.

Dolmetscherleistung

Die Leistung des Interpreters wurde in der Version Android 7.0 durch die Einführung von „mterp“ erheblich verbessert – einem Interpreter mit einem in Assembler geschriebenen Kernmechanismus zum Abrufen/Dekodieren/Interpretieren. Mterp ist dem schnellen Dalvik-Interpreter nachempfunden und unterstützt arm, arm64, x86, x86_64, mips und mips64. Für Computercode ist Art's mterp in etwa mit Dalviks schnellem Interpreter vergleichbar. In manchen Situationen kann es jedoch deutlich – und sogar dramatisch – langsamer sein:

  1. Leistung hervorrufen.
  2. String-Manipulation und andere starke Benutzer von Methoden, die in Dalvik als intrinsisch anerkannt sind.
  3. Höhere Stapelspeichernutzung.

Android 8.0 behebt diese Probleme.

Mehr Inlining

Seit Android 6.0 kann ART jeden Aufruf innerhalb derselben Dex-Dateien einbinden, konnte jedoch nur Blattmethoden aus verschiedenen Dex-Dateien einbinden. Für diese Einschränkung gab es zwei Gründe:

  1. Beim Inlining aus einer anderen Dex-Datei muss der Dex-Cache dieser anderen Dex-Datei verwendet werden, im Gegensatz zum Inlining derselben Dex-Datei, bei dem lediglich der Dex-Cache des Aufrufers wiederverwendet werden könnte. Der Dex-Cache wird im kompilierten Code für einige Anweisungen wie statische Aufrufe, String-Laden oder Klassenladen benötigt.
  2. Die Stack-Maps kodieren nur einen Methodenindex innerhalb der aktuellen Dex-Datei.

Um diese Einschränkungen zu beheben, bietet Android 8.0 Folgendes:

  1. Entfernt den Dex-Cache-Zugriff aus kompiliertem Code (siehe auch Abschnitt „Dex-Cache-Entfernung“)
  2. Erweitert die Stack-Map-Kodierung.

Verbesserungen bei der Synchronisierung

Das ART-Team hat die MonitorEnter/MonitorExit-Codepfade optimiert und unsere Abhängigkeit von herkömmlichen Speicherbarrieren auf ARMv8 reduziert, indem wir sie, wo möglich, durch neuere Anweisungen (Erfassen/Freigeben) ersetzt haben.

Schnellere native Methoden

Mit den Annotationen @FastNative und @CriticalNative sind schnellere native Aufrufe des Java Native Interface (JNI) verfügbar. Diese integrierten ART-Laufzeitoptimierungen beschleunigen JNI-Übergänge und ersetzen die inzwischen veraltete !bang-JNI -Notation. Die Anmerkungen haben keine Auswirkungen auf nicht-native Methoden und sind nur für Plattform-Java-Sprachcode im bootclasspath verfügbar (keine Play Store-Updates).

Die @FastNative Annotation unterstützt nicht statische Methoden. Verwenden Sie dies, wenn eine Methode als Parameter oder Rückgabewert auf ein jobject zugreift.

Die Annotation @CriticalNative bietet eine noch schnellere Möglichkeit, native Methoden auszuführen, mit den folgenden Einschränkungen:

  • Methoden müssen statisch sein – keine Objekte für Parameter, Rückgabewerte oder ein implizites this .
  • An die native Methode werden nur primitive Typen übergeben.
  • Die native Methode verwendet in ihrer Funktionsdefinition nicht die Parameter JNIEnv und jclass .
  • Die Methode muss bei RegisterNatives registriert werden, anstatt sich auf eine dynamische JNI-Verknüpfung zu verlassen.

@FastNative kann die Leistung nativer Methoden um das Dreifache und @CriticalNative um das Fünffache verbessern. Beispiel: Ein JNI-Übergang, gemessen auf einem Nexus 6P-Gerät:

Aufruf des Java Native Interface (JNI). Ausführungszeit (in Nanosekunden)
Regelmäßiges JNI 115
!bang JNI 60
@FastNative 35
@CriticalNative 25