ART-Verbesserungen in Android 8.0

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

Gleichzeitiger Komprimierungs-Speicherbereiniger

Wie auf der Google I/O angekündigt, bietet ART in Android 8.0 einen neuen gleichzeitigen Komprimierungs-Garbage Collector (GC). Dieser ‑Sammler komprimiert den Heap jedes Mal, wenn der 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 die Vorteile:

  • Der GC komprimiert den Heap immer: 32% kleinere Heap-Größen im Vergleich zu Android 7.0.
  • Die Komprimierung ermöglicht die threadlokale Zuordnung von Bump-Pointer-Objekten: Die Zuordnungen sind 70% schneller als in Android 7.0.
  • Bietet im Vergleich zum Android 7.0-GC 85% kürzere Pausierungszeiten für den H2-Benchmark.
  • Die Pausenzeiten skalieren nicht mehr mit der Heap-Größe. Apps sollten große Heaps verwenden können, ohne dass es zu Rucklern kommt.
  • Details zur GC-Implementierung – Leseschranken:
    • Leseschranken sind ein kleiner Arbeitsaufwand, der für jedes gelesene Objektfeld erforderlich ist.
    • Sie werden im Compiler optimiert, können aber einige Anwendungsfälle verlangsamen.

Optimierungen für Schleifen

In der Android 8.0-Version werden von ART eine Vielzahl von Schleifenoptimierungen eingesetzt:

  • Begrenzungsprüfungen eliminieren
    • Statisch: Die Bereiche liegen nachweislich innerhalb der Grenzen zur Kompilierungszeit.
    • Dynamisch: Laufzeittests sorgen dafür, dass Schleifen innerhalb der Grenzen bleiben (sonst wird die Deaktivierung aktiviert).
  • Eliminierung von Induktionsvariablen
    • Dead-Induction entfernen
    • Ersetzen Sie die Induktion, die nur nach der Schleife verwendet wird, durch geschlossene Ausdrücke.
  • Beseitigung von Dead Code im Loop-Body, Entfernung ganzer Schleifen, die nicht mehr benötigt werden
  • Stärkereduzierung
  • Schleifentransformationen: Umkehrung, Vertauschen, Aufteilen, Aufrollen, Unimodular usw.
  • SIMD-Optimierung (auch Vektorisierung genannt)

Der Schleifenoptimierer befindet sich im ART-Compiler in einem eigenen Optimierungsdurchlauf. Die meisten Optimierungen von Schleifen ähneln denen an anderen Stellen. Bei einigen Optimierungen, bei denen die CFG auf eine ungewöhnlich aufwendige Weise umgeschrieben wird, können Probleme auftreten, da die meisten CFG-Dienstprogramme (siehe nodes.h) darauf ausgerichtet sind, eine CFG zu erstellen, nicht umzuschreiben.

Analyse der Klassenhierarchie

ART in Android 8.0 verwendet die Klassenhierarchieanalyse (CHA), eine Compileroptimierung, die virtuelle Aufrufe basierend auf den Informationen, die durch die Analyse von Klassenhierarchien generiert werden, in direkte Aufrufe devirtualisiert. Virtuelle Aufrufe sind teuer, da sie um eine VTable-Suche herum implementiert sind und mehrere abhängige Ladungen erfordern. Außerdem können virtuelle Aufrufe nicht inline eingefügt werden.

Hier eine Zusammenfassung der entsprechenden Verbesserungen:

  • Aktualisierung des Status der dynamischen Methode mit einer einzelnen Implementierung: Am Ende der Verknüpfungszeit der Klasse, wenn die VTable ausgefüllt wurde, führt ART einen Vergleich der Einträge mit der VTable 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“ gesetzt ist, devirtualisiert der Compiler den virtuellen Aufruf in einen direkten Aufruf und versucht dann, den direkten Aufruf einzufügen.
  • Ungültig machen des kompilierten Codes: Auch am Ende der Klassenverknüpfung, wenn die Informationen zur Einzelimplementierung aktualisiert werden, muss der kompilierte Code für alle Methoden, die zuvor eine Einzelimplementierung hatten, aber dieser Status jetzt ungültig ist, ungültig gemacht werden.
  • Deoptimierung: Bei live kompiliertem Code, der sich auf dem Stack befindet, wird die Deoptimierung eingeleitet, um den ungültigen kompilierten Code in den Interpretermodus zu zwingen, um die Korrektheit zu gewährleisten. Es wird ein neuer Deoptimierungsmechanismus verwendet, der eine Kombination 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 Funktion für Inline-Caches zeichnet zusätzliche Informationen zur Laufzeit in Profilen auf und verwendet diese, um der Vorabkompilierung dynamische Optimierungen hinzuzufügen.

Dexlayout

Dexlayout ist eine Bibliothek, die in Android 8.0 eingeführt wurde, um Dex-Dateien zu analysieren und gemäß einem Profil neu anzuordnen. Dexlayout zielt darauf ab, Informationen aus dem Laufzeit-Profiling zu verwenden, 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 aufgrund der verbesserten Lokalität bessere Speicherzugriffsmuster haben, was RAM spart und die Startzeit verkürzt.

Da Profilinformationen derzeit nur nach dem Ausführen von Apps verfügbar sind, wird dexlayout in die On-Device-Kompilierung von dex2oat während der Inaktivitätswartung eingebunden.

Entfernung des Dex-Cache

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

  • Strings (eine Referenz pro DexFile::StringId),
  • Typen (eine Referenz pro DexFile::TypeId),
  • Methoden (ein nativer Pointer 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 hatten. Unter Android 8.0 wurden alle Arrays mit Ausnahme des Methoden-Arrays entfernt.

Interpreterleistung

Mit der Einführung von „mterp“ – einem Interpreter mit einem in Assemblersprache geschriebenen Kernmechanismus zum Abrufen, Decodieren und Interpretieren – wurde die Interpreterleistung in Android 7.0 deutlich verbessert. Mterp ist dem schnellen Dalvik-Interpreter nachempfunden und unterstützt ARM, ARM64, x86, x86_64, MIPS und MIPS64. Für den Berechnungscode ist das mterp von Art ungefähr mit dem schnellen Interpreter von Dalvik vergleichbar. In einigen Fällen kann es jedoch deutlich und sogar drastisch langsamer sein:

  1. Leistung aufrufen
  2. Stringmanipulation und andere intensive Nutzer von Methoden, die in Dalvik als Intrinsik angesehen werden.
  3. Höhere Stack-Speichernutzung.

Mit Android 8.0 wurden diese Probleme behoben.

Mehr Inline-Inhalte

Seit Android 6.0 kann ART jeden Aufruf innerhalb derselben dex-Dateien vorab einfügen, aber nur untergeordnete Methoden aus verschiedenen dex-Dateien. Dafür gab es zwei Gründe:

  1. Beim Einfügen aus einer anderen Dex-Datei muss der Dex-Cache dieser anderen Dex-Datei verwendet werden. Beim Einfügen in dieselbe Dex-Datei kann dagegen einfach der Dex-Cache des Aufrufers wiederverwendet werden. Der Dex-Cache ist im kompilierten Code für einige Anweisungen wie statische Aufrufe, String-Ladevorgänge oder Klassenladevorgänge erforderlich.
  2. Die Stack-Maps codieren nur einen Methodenindex in der aktuellen dex-Datei.

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

  1. Entfernt den Zugriff auf den Dex-Cache aus dem kompilierten Code (siehe auch Abschnitt „Entfernen des Dex-Cache“)
  2. Erweitert die Codierung von Stapelkarten.

Verbesserungen bei der Synchronisierung

Das ART-Team hat die Codepfade von MonitorEnter/MonitorExit optimiert und die Abhängigkeit von traditionellen Speicherbarrieren unter ARMv8 reduziert, indem sie nach Möglichkeit durch neuere Anweisungen (Acquire/Release) ersetzt wurden.

Schnellere native Methoden

Mit den Anmerkungen @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 jetzt veraltete !bang JNI-Notation. Die Anmerkungen haben keine Auswirkungen auf nicht native Methoden und sind nur für Java-Code der Plattform auf der bootclasspath verfügbar (keine Play Store-Updates).

Die @FastNative-Anmerkung unterstützt nicht statische Methoden. Verwenden Sie diese Option, wenn eine Methode auf eine jobject als Parameter oder Rückgabewert zugreift.

Die @CriticalNative-Anmerkung 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 eine implizite this.
  • Nur primitive Typen werden an die native Methode übergeben.
  • Bei der nativen Methode werden die Parameter JNIEnv und jclass in der Funktionsdefinition nicht verwendet.
  • Die Methode muss bei RegisterNatives registriert sein und darf nicht auf dynamische JNI-Verknüpfungen angewiesen sein.

Mit @FastNative lässt sich die Leistung der nativen Methode um bis zu 3 Mal und mit @CriticalNative um bis zu 5 Mal steigern. Beispiel für eine JNI-Übertragung, die auf einem Nexus 6P gemessen wurde:

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