L'environnement d'exécution Android Runtime (ART) a été considérablement amélioré dans la version Android 8.0. La liste ci-dessous résume les améliorations que les fabricants d'appareils peuvent attendre d'ART.
Récupérateur de mémoire compactant simultané
Comme annoncé lors de Google I/O, ART propose un nouveau collecteur de déchets (GC) concurrent dans Android 8.0. Ce collecteur compacte le tas à chaque fois que GC s'exécute et pendant l'exécution de l'application, avec une seule courte pause pour traiter les racines de thread. Voici ses avantages:
- Le GC compacte toujours le tas de mémoire: tailles de tas de mémoire 32% plus petites en moyenne par rapport à Android 7.0.
- La compaction permet l'allocation d'objets de pointeur de bosse locale de thread: les allocations sont 70% plus rapides qu'avec 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 des tas de mémoire. Les applications doivent pouvoir utiliser de grands tas de mémoire sans se soucier des à-coups.
- Détail de l'implémentation de la GC : barrières de lecture :
- Les barrières de lecture représentent une petite quantité de travail effectuée pour chaque lecture de champ d'objet.
- Ils sont optimisés dans le compilateur, mais peuvent ralentir certains cas d'utilisation.
Optimisations des boucles
ART utilise de nombreuses optimisations de boucle dans la version Android 8.0:
- Éliminations des vérifications de limites
- Statique: les plages sont prouvées comme étant dans les limites au moment de la compilation
- Dynamique: les tests d'exécution garantissent que les boucles restent dans les limites (désactivation dans le cas contraire)
- Éliminations de variables d'induction
- Supprimer l'induction morte
- Remplacez l'induction utilisée uniquement après la boucle par des expressions fermées
- Suppression du code mort dans le corps de la boucle, suppression de boucles entières qui deviennent inactives
- Réduction de la force
- Transformations de boucle: inversion, interversion, fractionnement, déroulement, unimodularité, etc.
- SIMDisation (également appelée vectorisation)
L'optimiseur de boucle réside dans sa propre étape d'optimisation dans le compilateur ART. La plupart des optimisations de boucle sont similaires 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
ART dans Android 8.0 utilise l'analyse de la hiérarchie des classes (CHA), 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 des classes. Les appels virtuels sont coûteux, car ils sont implémentés autour d'une recherche de vtable et nécessitent quelques 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 de l'état de la méthode d'implémentation unique dynamique : à la fin du temps d'association de la classe, lorsque la table v a été renseignée, ART effectue une comparaison par entrée avec la table v de la super-classe.
- Optimisation du compilateur : le compilateur exploitera les informations d'implémentation unique d'une méthode. Si l'indicateur d'implémentation unique est défini pour une méthode A.foo, le compilateur dévirtualisera l'appel virtuel en appel direct et tentera d'intégrer l'appel direct en conséquence.
- Invalidation du code compilé : également à la fin du temps de liaison de classe lorsque les informations sur l'implémentation unique sont mises à jour, si la méthode A.foo qui avait auparavant une implémentation unique, mais que cet état est maintenant invalidé, tout code compilé qui dépend de l'hypothèse que la méthode A.foo a une implémentation unique doit être invalidé.
- Désoptimisation : pour le code compilé actif situé sur la pile, la désoptimisation est lancée pour forcer le code compilé invalidé en mode interpréteur afin de garantir son exactitude. Un nouveau mécanisme de déoptimisation, qui est un hybride de déoptimisation synchrone et asynchrone, sera utilisé.
Caches intégrés dans les fichiers .oat
ART utilise désormais des caches intégrés et optimise les sites d'appel pour lesquels suffisamment de données existent. La fonctionnalité de caches intégrés enregistre des informations d'exécution supplémentaires dans des profils et les utilise pour ajouter des optimisations dynamiques à la compilation anticipée.
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 d'exécution pour réorganiser les sections du fichier dex lors de la compilation de maintenance à l'arrêt sur l'appareil. En regroupant les parties du fichier dex auxquelles on accède souvent ensemble, les programmes peuvent bénéficier de meilleurs modèles 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 que lorsque les applications ont été exécutées, dexlayout est intégré à la compilation sur l'appareil de dex2oat 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 précédemment résolus. 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ète ont été considérablement améliorées dans la version Android 7.0 avec l'introduction de "mterp", un interprète doté d'un mécanisme de récupération/décodage/interprétation de base écrit en langage d'assemblage. Mterp est modélisé sur l'interprète Dalvik rapide et est compatible avec les architectures arm, arm64, x86, x86_64, mips et mips64. Pour le code de calcul, mterp d'Art est à peu près comparable à l'interprète rapide de Dalvik. Toutefois, dans certains cas, il peut être beaucoup plus lent, voire extrêmement lent:
- Performances d'appel
- 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 la pile.
Android 8.0 résout ces problèmes.
Plus d'intégration
Depuis Android 6.0, ART peut intégrer n'importe quel appel dans les mêmes fichiers DEX, mais ne pouvait intégrer que les méthodes de feuilles de différents fichiers DEX. Cette limitation 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 ne peut que réutiliser le cache DEX de l'appelant. Le cache dex est nécessaire dans le code compilé pour quelques instructions telles que les appels statiques, le chargement de chaîne ou le chargement de classe.
- Les mappages de pile n'encodent qu'un indice de méthode dans le fichier DEX actuel.
Pour résoudre ces limites, Android 8.0:
- Supprime l'accès au cache DEX du code compilé (voir également la section "Suppression du cache DEX").
- Élargit le codage de la carte de pile.
Améliorations de la synchronisation
L'équipe ART a affiné les chemins de code MonitorEnter/MonitorExit et a réduit notre dépendance aux barrières de mémoire traditionnelles sur ARMv8, en les remplaçant par des instructions plus récentes (acquisition/libération) dans la mesure du 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 du runtime ART intégrées 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 de langage Java de la plate-forme sur le bootclasspath
(aucune mise à jour du Play Store).
L'annotation @FastNative
est compatible avec les méthodes non statiques. Utilisez-le si une méthode accède à un jobject
en tant que paramètre ou valeur de retour.
L'annotation @CriticalNative
fournit un moyen encore plus rapide d'exécuter des méthodes natives, avec les restrictions suivantes:
-
Les méthodes doivent être statiques. Aucun objet ne doit être utilisé pour les paramètres, les valeurs de retour 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 sa définition de 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:
Appel de l'interface Java Native Interface (JNI) | Durée d'exécution (en nanosecondes) |
---|---|
JNI standard | 115 |
!bang JNI | 60 |
@FastNative |
35 |
@CriticalNative |
25 |