L'environnement d'exécution Android (ART) a été considérablement amélioré dans la version Android 8.0. La liste ci-dessous récapitule les améliorations auxquelles les fabricants d'appareils peuvent s'attendre dans ART.
Récupérateur de mémoire avec compactage simultané
Comme annoncé lors de Google I/O, ART inclut un nouveau compacteur de garbage collector (GC) concurrent dans Android 8.0. Ce collecteur compacte le tas chaque fois que le GC s'exécute et pendant l'exécution de l'application, avec une seule brève pause pour le traitement des racines de thread. Voici ses avantages :
- Le GC compacte toujours le tas de mémoire : la taille du tas de mémoire est en moyenne 32 % plus petite que sur Android 7.0.
- La compaction permet l'allocation d'objets pointeurs de bump locaux aux threads : les allocations sont 70 % plus rapides que dans Android 7.0.
- Offre des temps de pause 85 % plus courts pour le benchmark H2 par rapport au GC Android 7.0.
- Les temps de pause ne sont plus mis à l'échelle en fonction de la taille du tas. Les applications devraient pouvoir utiliser de grands tas sans se soucier des saccades.
- Détails de l'implémentation de GC : barrières de lecture :
- Les barrières de lecture représentent une petite quantité de travail effectuée pour chaque champ d'objet lu.
- Elles sont optimisées dans le compilateur, mais peuvent ralentir certains cas d'utilisation.
Optimisations de boucle
ART utilise une grande variété d'optimisations de boucle dans la version 8.0 d'Android :
- Éliminations des vérifications des limites
- Statique : les plages sont prouvées comme étant dans les limites au moment de la compilation.
- Tests dynamiques d'exécution pour s'assurer que les boucles restent dans les limites (sinon, désoptimisation)
- Éliminations de variables d'induction
- Supprimer l'induction morte
- Remplacer l'induction utilisée uniquement après la boucle par des expressions de forme fermée
- Élimination du code mort dans le corps de la boucle, suppression des boucles entières qui deviennent mortes
- Réduction de l'intensité
- Transformations de boucles : inversion, échange, fractionnement, déroulement, unimodulaire, etc.
- SIMDisation (également appelée vectorisation)
L'optimiseur de boucle réside dans son propre pass d'optimisation dans le compilateur ART. La plupart des optimisations de boucle sont semblables aux optimisations et simplifications ailleurs. Des difficultés surviennent avec certaines optimisations qui réécrivent le CFG de manière plus élaborée que d'habitude, car la plupart des utilitaires CFG (voir nodes.h) se concentrent sur la création d'un CFG, et non sur sa réécriture.
Analyse de la hiérarchie des classes
Dans Android 8.0, ART utilise l'analyse de la hiérarchie des classes (CHA, Class Hierarchy Analysis), une optimisation du compilateur qui dévirtualise les appels virtuels en appels directs en fonction des informations générées par l'analyse des hiérarchies de classes. Les appels virtuels sont coûteux, car ils sont implémentés autour d'une recherche dans une table virtuelle et nécessitent plusieurs chargements dépendants. De plus, les appels virtuels ne peuvent pas être intégrés.
Voici un résumé des améliorations associées :
- Mise à jour dynamique de l'état de la méthode d'implémentation unique : à la fin de l'association de classe, lorsque la table virtuelle a été remplie, ART effectue une comparaison entrée par entrée avec la table virtuelle de la classe parente.
- Optimisation du compilateur : le compilateur tire parti des informations d'implémentation unique d'une méthode. Si un indicateur d'implémentation unique est défini pour une méthode A.foo, le compilateur dévirtualisera l'appel virtuel en un appel direct, puis tentera d'intégrer l'appel direct.
- Invalidation du code compilé : également à la fin de la période d'association de classe, lorsque les informations d'implémentation unique sont mises à jour. Si la méthode A.foo avait auparavant une implémentation unique, mais que cet état est désormais invalidé, tout le code compilé qui dépend de l'hypothèse selon laquelle la méthode A.foo a une implémentation unique doit être invalidé.
- Déoptimisation : pour le code compilé en direct qui se trouve dans la pile, la déoptimisation sera lancée pour forcer le code compilé invalidé à passer en mode interpréteur afin de garantir l'exactitude. Un nouveau mécanisme de désoptimisation hybride (synchrone et asynchrone) sera utilisé.
Caches intégrées dans les fichiers .oat
ART utilise désormais des caches intégrés et optimise les sites d'appel pour lesquels il existe suffisamment de données. La fonctionnalité de caches intégrés enregistre des informations d'exécution supplémentaires dans les profils et les utilise pour ajouter des optimisations dynamiques à la compilation AOT (Ahead-Of-Time).
Dexlayout
Dexlayout est une bibliothèque introduite dans Android 8.0 pour analyser les fichiers dex et les réorganiser en fonction d'un profil. Dexlayout vise à utiliser les informations de profilage de l'exécution pour réorganiser les sections du fichier dex lors de la compilation de maintenance inactive sur l'appareil. En regroupant les parties du fichier dex qui sont souvent consultées ensemble, les programmes peuvent bénéficier de meilleurs schémas d'accès à la mémoire grâce à une localité améliorée, ce qui permet d'économiser de la RAM et de réduire le temps de démarrage.
Étant donné que les informations de profil ne sont actuellement disponibles qu'après l'exécution des applications, dexlayout est intégré à la compilation dex2oat sur l'appareil lors de la maintenance inactive.
Suppression du cache Dex
Jusqu'à Android 7.0, l'objet DexCache possédait quatre grands tableaux, proportionnels au nombre de certains éléments du DexFile, à savoir :
- chaînes (une référence par DexFile::StringId),
- types (une référence par DexFile::TypeId),
- méthodes (un pointeur natif par DexFile::MethodId),
- (un pointeur natif par DexFile::FieldId).
Ces tableaux étaient utilisés pour récupérer rapidement les objets que nous avions résolus précédemment. Dans Android 8.0, tous les tableaux ont été supprimés, à l'exception du tableau des méthodes.
Performances de l'interprète
Les performances de l'interpréteur ont été considérablement améliorées dans la version 7.0 d'Android avec l'introduction de "mterp", un interpréteur doté d'un mécanisme de récupération/décodage/interprétation de base écrit en langage assembleur. Mterp est modélisé d'après l'interpréteur Dalvik rapide et est compatible avec arm, arm64, x86, x86_64, mips et mips64. Pour le code de calcul, mterp d'Art est à peu près comparable à l'interpréteur rapide de Dalvik. Toutefois, dans certaines situations, elle peut être beaucoup plus lente, voire considérablement plus lente :
- Invoquez les performances.
- Manipulation de chaînes et autres utilisateurs intensifs de méthodes reconnues comme intrinsèques dans Dalvik.
- Utilisation plus élevée de la mémoire de pile.
Android 8.0 résout ces problèmes.
Plus d'intégration
Depuis Android 6.0, ART peut insérer n'importe quel appel dans les mêmes fichiers dex, mais ne pouvait insérer que les méthodes feuilles à partir de différents fichiers dex. Cette limite s'explique par deux raisons :
- L'intégration à partir d'un autre fichier dex nécessite d'utiliser le cache dex de cet autre fichier dex, contrairement à l'intégration du même fichier dex, qui peut simplement réutiliser le cache dex de l'appelant. Le cache dex est nécessaire dans le code compilé pour quelques instructions, comme les appels statiques, le chargement de chaînes ou le chargement de classes.
- Les stack maps n'encodent qu'un index de méthode dans le fichier dex actuel.
Pour résoudre ces limitations, Android 8.0 :
- Supprime l'accès au cache dex du code compilé (voir également la section "Suppression du cache dex")
- Étend l'encodage de la carte de pile.
Améliorations de la synchronisation
L'équipe ART a ajusté les chemins de code MonitorEnter/MonitorExit et réduit notre dépendance aux barrières de mémoire traditionnelles sur ARMv8, en les remplaçant par des instructions plus récentes (acquire/release) lorsque cela était possible.
Méthodes natives plus rapides
Des appels natifs plus rapides à l'interface JNI (Java Native Interface) sont disponibles à l'aide des annotations @FastNative
et @CriticalNative
. Ces optimisations intégrées du runtime ART accélèrent les transitions JNI et remplacent la notation !bang JNI désormais obsolète. Les annotations n'ont aucun effet sur les méthodes non natives et ne sont disponibles que pour le code du langage Java de la plate-forme sur bootclasspath
(aucune mise à jour du Play Store).
L'annotation @FastNative
est compatible avec les méthodes non statiques. Utilisez cette annotation si une méthode accède à un jobject
en tant que paramètre ou valeur de retour.
L'annotation @CriticalNative
permet d'exécuter des méthodes natives encore plus rapidement, avec les restrictions suivantes :
-
Les méthodes doivent être statiques : aucun objet pour les paramètres, les valeurs renvoyées ni un
this
implicite. - Seuls les types primitifs sont transmis à la méthode native.
-
La méthode native n'utilise pas les paramètres
JNIEnv
etjclass
dans la définition de sa fonction. -
La méthode doit être enregistrée auprès de
RegisterNatives
au lieu de s'appuyer sur l'association JNI dynamique.
@FastNative
peut améliorer les performances des méthodes natives jusqu'à trois fois, et @CriticalNative
jusqu'à cinq fois. Par exemple, une transition JNI mesurée sur un appareil Nexus 6P :
Invocation de l'interface JNI (Java Native Interface) | Temps d'exécution (en nanosecondes) |
---|---|
JNI standard | 115 |
!bang JNI | 60 |
@FastNative |
35 |
@CriticalNative |
25 |