Die Android-Laufzeitumgebung (Android Runtime, ART) wurde in Android 8.0 erheblich verbessert. In der folgenden Liste sind die Verbesserungen zusammengefasst, die Gerätehersteller in ART erwarten können.
Gleichzeitige automatische Speicherbereinigung
Wie auf der Google I/O angekündigt, bietet ART in Android 8.0 eine neue gleichzeitige automatische Speicherbereinigung (Garbage Collection, GC). Bei jeder Ausführung der automatischen Speicherbereinigung wird der Heap komprimiert, während die App ausgeführt wird. Es gibt nur eine kurze Pause für die Verarbeitung von Thread-Roots. Vorteile:
- Die automatische Speicherbereinigung komprimiert immer den Heap: Die Heap-Größen sind im Durchschnitt 32% kleiner als in Android 7.0.
- Durch die Komprimierung kann die Objektauslastung mit Bump-Pointer für Thread-Local-Speicher erfolgen: Die Zuweisungen sind 70% schneller als in Android 7.0.
- Die Pausenzeiten für den H2-Benchmark sind im Vergleich zur automatischen Speicherbereinigung in Android 7.0 um 85% kürzer.
- Die Pausenzeiten skalieren nicht mehr mit der Heap-Größe. Apps sollten große Heaps verwenden können, ohne sich um Ruckler sorgen zu müssen.
- Details zur Implementierung der automatischen Speicherbereinigung – Lesesperren:
- Lesesperren sind ein kleiner Aufwand für jedes gelesene Objektfeld.
- Sie werden im Compiler optimiert, können aber einige Anwendungsfälle verlangsamen.
Schleifenoptimierungen
In Android 8.0 werden in ART 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 (ansonsten Deoptimierung).
- Entfernung von Induktionsvariablen
- Entfernung von nicht mehr benötigten Induktionen
- Ersetzung von Induktionen, die erst nach der Schleife verwendet werden, durch Ausdrücke in geschlossener Form Ausdrücke
- Entfernung von nicht mehr benötigtem Code im Schleifenkörper, Entfernung ganzer Schleifen, die nicht mehr benötigt werden
- Reduzierung der Kraft
- Schleifentransformationen: Umkehrung, Austausch, Aufteilung, Entrollung, unimodular usw.
- SIMD-Verarbeitung (auch Vektorisierung genannt)
Der Schleifenoptimierer befindet sich in einem eigenen Optimierungsschritt im ART-Compiler. Die meisten Schleifenoptimierungen ähneln Optimierungen und Vereinfachung an anderer Stelle. Bei einigen Optimierungen, die den 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 konzentrieren.
Analyse der Klassenhierarchie
ART in Android 8.0 verwendet die Analyse der Klassenhierarchie (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 um eine vtable-Suche herum implementiert werden und einige abhängige Ladevorgänge erfordern. Außerdem können virtuelle Aufrufe nicht inline ausgeführt werden.
Hier eine Zusammenfassung der entsprechenden Verbesserungen:
- Dynamische Aktualisierung des Status der Methode mit einer einzelnen Implementierung: Am Ende der Klassen Verknüpfungszeit, wenn die vtable-Tabelle gefüllt wurde, führt ART einen Eintrag-für-Eintrag Vergleich mit der vtable-Tabelle der Superklasse durch.
- Compileroptimierung: Der Compiler nutzt die Informationen zur einzelnen Implementierung einer Methode. Wenn für eine Methode A.foo das Flag für die einzelne Implementierung festgelegt ist, devirtualisiert der Compiler den virtuellen Aufruf in einen direkten Aufruf und versucht außerdem, den direkten Aufruf inline auszuführen.
- Ungültigmachung von kompiliertem Code: Wenn am Ende der Klassenverknüpfungszeit auch die Informationen zur einzelnen Implementierung aktualisiert werden und die Methode A.foo zuvor eine einzelne Implementierung hatte, dieser Status aber jetzt ungültig ist, muss der gesamte kompilierte Code, der auf der Annahme beruht, dass die Methode A.foo eine einzelne Implementierung hat, ungültig gemacht werden.
- Deoptimierung: Für aktiven kompilierten Code, der sich im Stack befindet, wird eine Deoptimierung initiiert, um den ungültigen kompilierten Code in den Interpretermodus zu zwingen und so die Korrektheit zu gewährleisten. Es wird ein neuer Mechanismus zur Deoptimierung 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. Mit der Funktion für Inline-Caches werden zusätzliche Laufzeitinformationen in Profilen erfasst und verwendet, um dynamische Optimierungen zur AOT-Kompilierung hinzuzufügen.
Dexlayout
Dexlayout ist eine in Android 8.0 eingeführte Bibliothek, mit der DEX-Dateien analysiert und gemäß einem Profil neu angeordnet werden können. Dexlayout verwendet Laufzeitprofilinformationen, um Abschnitte der DEX-Datei während der Kompilierung im Leerlauf 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 Speicherzugriffsmuster durch eine verbesserte Lokalität haben, wodurch RAM gespart und die Startzeit verkürzt wird.
Da Profilinformationen derzeit erst verfügbar sind, nachdem Apps ausgeführt wurden, ist Dexlayout in die On-Device-Kompilierung von dex2oat während der Leerlauf Wartung integriert.
Entfernung des DEX-Cache
Bis Android 7.0 enthielt das DexCache-Objekt vier große Arrays, die proportional zu der Anzahl bestimmter Elemente in der DexFile waren, nämlich:
- Strings (eine Referenz pro DexFile::StringId)
- Typen (eine Referenz pro DexFile::TypeId)
- Methoden (ein nativer Pointer pro DexFile::MethodId)
- Felder (ein nativer Pointer pro DexFile::FieldId)
Diese Arrays wurden verwendet, um zuvor aufgelöste Objekte schnell abzurufen. In Android 8.0 wurden alle Arrays außer dem Methodenarray entfernt.
Interpreterleistung
Die Interpreterleistung wurde in Android 7.0 mit der Einführung von „mterp“ erheblich verbessert. Dabei handelt es sich um einen Interpreter mit einem Kernmechanismus zum Abrufen, Decodieren und Interpretieren, der in Assemblersprache geschrieben wurde. Mterp ist nach dem schnellen Dalvik-Interpreter modelliert 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 er jedoch deutlich und sogar drastisch langsamer sein:
- Leistung beim Aufrufen
- String-Manipulation und andere Methoden, die in Dalvik als intrinsische Funktionen erkannt werden und viele Ressourcen verbrauchen
- Höhere Nutzung des Stack-Speichers
In Android 8.0 wurden diese Probleme behoben.
Mehr Inlining
Seit Android 6.0 kann ART jeden Aufruf innerhalb derselben DEX-Dateien inline ausführen, aber konnte nur Blattmethoden aus verschiedenen DEX-Dateien inline ausführen. Für diese Einschränkung gab es zwei Gründe:
- 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, String-Ladevorgänge oder Klassenladevorgänge benötigt.
- Die Stack-Maps codieren nur einen Methodenindex innerhalb der aktuellen DEX-Datei.
Um diese Einschränkungen zu beheben, bietet Android 8.0 folgende Verbesserungen:
- Entfernung des DEX-Cache-Zugriffs aus kompiliertem Code (siehe auch Abschnitt „Entfernung des DEX-Cache“)
- Erweiterung der Stack-Map-Codierung
Verbesserungen bei der Synchronisierung
Das ART-Team hat die Codepfade MonitorEnter/MonitorExit optimiert und die Abhängigkeit von herkömmlichen Speichersperren auf ARMv8 reduziert. Stattdessen werden nach Möglichkeit neuere Anweisungen (acquire/release) verwendet.
Schnellere native Methoden
Schnellere native Aufrufe der Java Native Interface (JNI) sind mit
den @FastNative und @CriticalNative Annotationen möglich. Diese integrierten ART-Laufzeit
Optimierungen beschleunigen JNI-Übergänge und ersetzen die jetzt veraltete
!bang JNI-Notation. Die Annotationen haben keine Auswirkungen auf nicht native
Methoden und sind nur für Plattform-Java-Code im
bootclasspath verfügbar (keine Play Store-Updates).
Die Annotation @FastNative unterstützt nicht statische Methoden. Verwenden Sie sie
wenn eine Methode auf ein jobject als Parameter oder Rückgabewert zugreift.
Die @CriticalNative Annotation 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. - Nur primitive Typen werden an die native Methode übergeben.
-
Die native Methode verwendet die
JNIEnvundjclassParameter nicht in ihrer Funktionsdefinition. -
Die Methode muss mit
RegisterNativesregistriert werden, anstatt auf die dynamische JNI-Verknüpfung zu setzen.
@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:
| Java Native Interface (JNI)-Aufruf | Ausführungszeit (in Nanosekunden) |
|---|---|
| Reguläre JNI | 115 |
| !bang JNI | 60 |
@FastNative |
35 |
@CriticalNative |
25 |