ART-Verbesserungen in Android 8.0

Die Android-Laufzeit (ART) wurde in Android 8.0 deutlich verbessert. In der folgenden Liste sind die Verbesserungen zusammengefasst, die Gerätehersteller in ART erwarten können.

Gleichzeitige Speicherbereinigung

Wie auf der Google I/O angekündigt, bietet ART in Android 8.0 einen neuen Garbage Collector (GC) für die gleichzeitige Komprimierung. Dieser Collector komprimiert den Heap jedes Mal, wenn die Garbage Collection ausgeführt wird, und während die App ausgeführt wird. Dabei gibt es nur eine kurze Pause für die Verarbeitung von Thread-Roots. Hier sind die Vorteile:

  • Die Garbage Collection komprimiert den Heap immer: Die Heap-Größen sind im Vergleich zu Android 7.0 durchschnittlich 32% kleiner.
  • Durch die Verdichtung können Thread-lokale Bump-Pointer-Objekte zugewiesen werden: Zuweisungen sind 70% schneller als in Android 7.0.
  • Bietet 85% kürzere Pausenzeiten für den H2-Benchmark im Vergleich zum Android 7.0-GC.
  • Pausenzeiten werden nicht mehr mit der Heap-Größe skaliert. Apps sollten große Heaps verwenden können, ohne dass es zu Rucklern kommt.
  • GC-Implementierungsdetail – Lesesperren:
    • Lesesperren sind ein geringer Aufwand, der für jedes gelesene Objektfeld anfällt.
    • Diese werden im Compiler optimiert, können aber einige Anwendungsfälle verlangsamen.

Schleifenoptimierungen

In ART in Android 8.0 werden eine Vielzahl von Schleifenoptimierungen eingesetzt:

  • Entfernung von Bereichsprüfungen
    • Statisch: Bereiche sind zur Kompilierzeit nachweislich innerhalb der Grenzen.
    • Dynamisch: Laufzeittests sorgen dafür, dass Schleifen innerhalb der Grenzen bleiben (andernfalls wird die Optimierung aufgehoben).
  • Eliminierung von Induktionsvariablen
    • Entfernen von nicht mehr funktionierenden Induktionsfeldern
    • Induktion, die nur nach der Schleife verwendet wird, durch geschlossene Ausdrücke ersetzen
  • Entfernen von nicht mehr benötigtem Code im Schleifenkörper, Entfernen ganzer Schleifen, die nicht mehr benötigt werden
  • Festigkeitsverlust
  • Schleifentransformationen: Umkehrung, Vertauschung, Aufteilung, Entrollen, unimodular usw.
  • SIMD-Optimierung (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 aufwendiger als üblich umschreiben, treten Probleme auf, da sich die meisten CFG-Dienstprogramme (siehe nodes.h) auf das Erstellen eines CFG und nicht auf das Umschreiben eines CFG konzentrieren.

Analyse der Klassenhierarchie

ART in Android 8.0 verwendet die Klassenhierarchieanalyse (Class Hierarchy Analysis, CHA), eine Compileroptimierung, die virtuelle Aufrufe basierend auf den durch die Analyse von Klassenhierarchien generierten Informationen in direkte Aufrufe devirtualisiert. Virtuelle Aufrufe sind teuer, da sie auf einer VTable-Suche basieren und einige abhängige Ladevorgänge erfordern. Auch virtuelle Aufrufe können nicht inline ausgeführt werden.

Hier finden Sie eine Zusammenfassung der entsprechenden Verbesserungen:

  • Dynamische Aktualisierung des Status der Methode mit einer einzelnen Implementierung: Am Ende der Klassenverknüpfungszeit, wenn die virtuelle Methodestabelle (vtable) gefüllt wurde, führt ART einen Eintrag-für-Eintrag-Vergleich mit der virtuellen Methodestabelle der Superklasse durch.
  • Compileroptimierung: Der Compiler nutzt die Informationen zur einzelnen Implementierung einer Methode. Wenn für eine Methode A.foo das Flag „single-implementation“ festgelegt ist, wird der virtuelle Aufruf vom Compiler in einen direkten Aufruf devirtualisiert und der direkte Aufruf wird in der Folge inline ausgeführt.
  • Ungültigmachung von kompiliertem Code: Auch am Ende der Klassenverknüpfungszeit, wenn Informationen zur Einzelimplementierung aktualisiert werden, muss der kompilierte Code, der auf der Annahme beruht, dass die Methode „A.foo“ eine Einzelimplementierung hat, ungültig gemacht werden, wenn die Methode „A.foo“ zuvor eine Einzelimplementierung hatte, dieser Status aber jetzt ungültig ist.
  • Deoptimierung: Bei live kompiliertem Code, der sich im Stack befindet, wird die Deoptimierung eingeleitet, um den ungültigen kompilierten Code in den Interpretermodus zu zwingen und so die Richtigkeit zu gewährleisten. Es wird ein neuer Mechanismus zur Deoptimierung 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. Mit der Funktion für Inline-Caches werden zusätzliche Laufzeitinformationen in Profilen aufgezeichnet und verwendet, um der Ahead-of-Time-Kompilierung dynamische Optimierungen hinzuzufügen.

Dexlayout

Dexlayout ist eine in Android 8.0 eingeführte Bibliothek, mit der sich DEX-Dateien analysieren und gemäß einem Profil neu anordnen lassen. Dexlayout verwendet Informationen aus dem Laufzeitprofiling, um Abschnitte der DEX-Datei während der Inaktivitätswartung auf dem Gerät neu anzuordnen. Durch das Gruppieren von Teilen der DEX-Datei, auf die häufig gemeinsam zugegriffen wird, können Programme bessere Speichermuster durch eine verbesserte Lokalität aufweisen. Dadurch wird RAM gespart und die Startzeit verkürzt.

Da Profilinformationen derzeit erst verfügbar sind, nachdem Apps ausgeführt wurden, ist dexlayout in die On-Device-Kompilierung von dex2oat während der Wartung im Leerlauf integriert.

Dex-Cache entfernen

Bis Android 7.0 enthielt das DexCache-Objekt vier große Arrays, die proportional zur Anzahl bestimmter Elemente in der DexFile waren:

  • Strings (ein Verweis 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 für den schnellen Abruf von Objekten verwendet, die wir zuvor aufgelöst haben. In Android 8.0 wurden alle Arrays mit Ausnahme des Methoden-Arrays entfernt.

Interpreter-Leistung

Die Leistung des Interpreters wurde mit der Einführung von „mterp“ in Android 7.0 deutlich verbessert. „mterp“ ist ein Interpreter mit einem in Assemblersprache geschriebenen Fetch-/Decode-/Interpret-Mechanismus. Mterp ist dem schnellen Dalvik-Interpreter nachempfunden und unterstützt arm, arm64, x86, x86_64, mips und mips64. Für Rechencode ist der mterp von Art in etwa mit dem schnellen Interpreter von Dalvik vergleichbar. In einigen Situationen kann es jedoch deutlich und sogar drastisch langsamer sein:

  1. Aufrufleistung
  2. String-Manipulation und andere Methoden, die in Dalvik als intrinsisch erkannt werden.
  3. Höhere Stack-Arbeitsspeichernutzung.

In Android 8.0 wurden diese Probleme behoben.

Mehr Inlining

Seit Android 6.0 kann ART jeden Aufruf in denselben DEX-Dateien inline ausführen, aber nur Blattmethoden aus verschiedenen DEX-Dateien. Für diese Einschränkung gab es zwei Gründe:

  1. Für das Inlining aus einer anderen DEX-Datei muss der DEX-Cache dieser anderen DEX-Datei verwendet werden. Beim Inlining aus derselben DEX-Datei kann der DEX-Cache des Aufrufers wiederverwendet werden. Der DEX-Cache wird im kompilierten Code für einige Anweisungen wie statische Aufrufe, das Laden von Strings oder das Laden von Klassen benötigt.
  2. Die Stackmaps codieren nur einen Methodenindex innerhalb der aktuellen DEX-Datei.

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

  1. Entfernt den Zugriff auf den DEX-Cache aus kompiliertem Code (siehe auch den Abschnitt „Entfernung des DEX-Cache“)
  2. Erweitert die Codierung von Stapelkarten.

Verbesserungen bei der Synchronisierung

Das ART-Team hat die Codepfade „MonitorEnter“/„MonitorExit“ optimiert und die Abhängigkeit von herkömmlichen Memory Barriers auf ARMv8 verringert. Wo möglich, wurden sie durch neuere (acquire/release)-Befehle ersetzt.

Schnellere native Methoden

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

Die Annotation @FastNative unterstützt nicht statische Methoden. Verwenden Sie diese Option, wenn eine Methode auf ein jobject als Parameter oder Rückgabewert 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.
  • Bei der nativen Methode werden die Parameter JNIEnv und jclass nicht in der Funktionsdefinition verwendet.
  • Die Methode muss bei RegisterNatives registriert werden, anstatt sich auf die dynamische JNI-Verknüpfung zu verlassen.

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

Java Native Interface (JNI)-Aufruf Ausführungszeit (in Nanosekunden)
Reguläres JNI 115
!bang JNI 60
@FastNative 35
@CriticalNative 25